5 ms·
Admittedly I'm not a huge fan of having: slog.Info("hello, world", "user", os.Getenv("USER")) It's a little magical that "user" is a key. So what if you hav
by mrweasel 3y ago
Admittedly I'm not a huge fan of having:
slog.Info("hello, world", "user", os.Getenv("USER"))
It's a little magical that "user" is a key. So what if you have multiple key-value pairs? Arguably it most likely going to be obvious which is the keys, but having every other value be a key and the rest values seems a little clumsy.
I really like Pythons approach where you can have user="value" it makes things a bit more clear.
- packetlost 3y agoYeah, I agree. Passing in an optional `map[string]string` or something would be better, but then you get into having to either pass in `nil` every time you don't have the extra data or needing an entirely different function for with vs without the map
- masklinn 3y ago> Passing in an optional `map[string]string` or something would be better It would definitely not be better from the point of view of > We wanted slog to be fast.
- packetlost 3y agoA better interface/API is really what I meant. The performance characteristics are probably worth the tradeoff.
- returningfory2 3y agoPassing in a map would require an extra allocation for the map memory for each log line. I think the performance would probably not be great?
- packetlost 3y agoit depends. I believe map literals are stack allocated if they aren't shared across goroutines or globals.
- masklinn 3y agoWhile the hmap struct can be stack allocated if it does not escape, I’m pretty sure the buckets still need to be heap-allocated. I do not believe hmap has a “flat structure” variant which could live entirely on the stack, though I could be wrong.
- Ferret7446 3y agoIt's most definitely not. Logging is crucial for monitoring services, and making logging statements many times slower will either sink your service or push developers to avoid logging and making the service impossible to debug. Most log metadata will be attached by libraries and middleware, so service/application devs won't even see most of it.
- packetlost 3y agoI think you interpreted my comment the wrong way, I think the performance tradeoffs are worth the mediocre API design. My bad for making it ambiguous
- masklinn 3y ago> It's a little magical that "user" is a key. So what if you have multiple key-value pairs? You... add them afterwards? It's really just a plist (https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html https://www.gnu.org/software/emacs/manual/html_node/elisp/Pr...), that's hardly novel. The method takes any number of parameters and pairs them up as key, value. Or if you really hate yourself, you use LogAttrs with explicitly constructed Attr objects. > I really like Pythons approach where you can have user="value" it makes things a bit more clear. The trouble is, Go doesn't have keyword parameters so that doesn't work.
- radlad 3y agoIt sure has maps though... logrus famously uses `logrus.Fields{"key": "value"}`
- morelisp 3y agoAnd logrus is one of the slowest loggers by far, in part because of its heavy map usage.
- deleted 3y ago[deleted]
- dgsb 3y agoIt's also the reflect on the value part which slows down things. It could be improved but it's hard to do that without breaking interface. It's so widely used that a major bump wouldn't that useful.
- JonChesterfield 3y agoI'd expect it to be mostly called on map literals, at least a compile time constant set of keys. That should be amenable to a targeted compiler optimisation.
- taeric 3y agoPlist or named arguments doesn't really make too big of a difference, to my eyes. I do recommend Mapped Diagnostic Context style approaches, if you can do it. Passing all arguments that you think may be useful to logs gets unwieldy quickly. Looks particularly absurd when folks start adding parameters to functions for the sole purpose of letting you log them.
- nerdponx 3y agoI'm really happy there's a name for this idea. I've been looking for something like this.
- grose 3y agoIt’s not without precedence, for example: https://pkg.go.dev/strings#NewReplacer https://pkg.go.dev/strings#NewReplacer I don’t mind it. You can use LogAttrs if you want to be explicit. Although I do wonder if there’s anything tricky with the type system that is preventing something like this from being supported: https://go.dev/play/p/_YV7sYdnZ5V https://go.dev/play/p/_YV7sYdnZ5V
- eska 3y agoIf you had read more than the introduction you’d have found a paragraph about this in the middle and multiple at the end.
- baq 3y agothis is not uncommon. this is what I've been dealing with earlier today in Postgres: json_build_object ( VARIADIC "any" ) → json jsonb_build_object ( VARIADIC "any" ) → jsonb Builds a JSON object out of a variadic argument list. By convention, the argument list consists of alternating keys and values. Key arguments are coerced to text; value arguments are converted as per to_json or to_jsonb. json_build_object('foo', 1, 2, row(3,'bar')) → {"foo" : 1, "2" : {"f1":3,"f2":"bar"}}
- paulddraper 3y agoI'm really used to writing them on new lines. json_build_object( 'foo', 1, 2, row(3, 'bar') )
- jupp0r 3y agoI think Uber chose a better approach for their Go logging library called Zap [1] logger.Info("failed to fetch URL", // Structured context as strongly typed Field values. zap.String("url", url), zap.Int("attempt", 3), zap.Duration("backoff", time.Second), ) They also have zap.Error(err) which generates the "error" key as a convention. [1] https://github.com/uber-go/zap https://github.com/uber-go/zap
- bpicolo 3y agoAs mentioned in the article, Zap's SugaredLogger has roughly the same thing https://pkg.go.dev/go.uber.org/zap https://pkg.go.dev/go.uber.org/zap sugar.Infow("failed to fetch URL", "url", "http://example.com", "attempt", 3, "backoff", time.Second, ) Really slog presents mostly the same two APIs as Zap, with a single entrypoint.
- Groxx 3y ago`[level]w` supports both key/value pairs and individual `zap.Type(key, value)` values combined fwiw, so for that part the API is essentially the same too
- masklinn 3y agoSlog allows doing that by creating Attr structs directly.
- sa46 3y agoYou can reproduce that example with slog verbatim by changing zap to slog. For example: https://pkg.go.dev/golang.org/x/exp/slog#example-Group https://pkg.go.dev/golang.org/x/exp/slog#example-Group
- hamandcheese 3y agoThe same pattern can be seen in Java using Map.of(...)
- candiddevmike 3y agoToday I'm pulling this out of a context object, can I still do that?
- richardwhiuk 3y agoRust's slog ( https://docs.rs/slog/ https://docs.rs/slog/ ) does: use slog::info; ... info!("hello, world"; "user" => std::env::var("USER"));
- codetrotter 3y agoYup! Love the Rust slog crate. Initially thought OP was gonna be about Rust slog, before I saw the domain mention Go.
- masklinn 3y agoIt can do that because Rust’s macros can have their own mini language at the top level, and can transform that into whatever data structure they want under the cover. For better or for worse, Go doesn’t have that.
- masklinn 3y agoAlso funny story: in Perl “,” can be spelled “=>” specifically for this sort of use cases, when you write my %hash = ( Foo => “bar”, Baz => “qux”, ); the behaviour is no different than my %hash = ( Foo, “bar”, Baz, “qux”, ); it’s just that a literal plist in a hash context is interpreted as a hash. Hence “=>” being called “fat comma” in perl. Which means you can writer my %h = (a => b => c => d); or my %h = (a, b => c, d); they all mean the same thing.
- richardwhiuk 3y agoThat all sounds questionable.
- mlhpdx 3y agoIndeed. Logging a map-like object seems like a pretty basic expectation. Separate keys and values as parameters seem very C-like.
- yencabulator 3y agoPersonally I only intend to ever use LogAttrs/AddAttrs/GroupValue, for this reason. I really don't want to count evens and odds.
- AkshitGarg 3y agoYou use attrs [1] instead. I personally find this much more readable, and prefer this. slog.Info("hello, world", slog.String("user", os.Getenv("USER"))) [1]: https://pkg.go.dev/log/slog#hdr-Attrs_and_Values https://pkg.go.dev/log/slog#hdr-Attrs_and_Values