You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We propose a new package providing structured logging with levels. Structured logging adds key-value pairs to a human-readable output message to enable fast, accurate processing of large amounts of log data.
This is a huge API surface without any real production testing (AIUI). Perhaps it might be better to land it under golang.org/x for some time? Eg, like context, xerrors changes.
Reacted by Alessandro Arzilli, fsouza, Wilk, Tomáš Karela Procházka, deef, Ilia Choly, Andreeee, Komu Wairagu, Berkeley Martinez, Ainar Garipov and 17 more
Reacted by Alex, David Birks, Ian Foster, Tony Bai, Berkeley Martinez, Ewen Quimerc'h, Ivan Cheremisin, René Jochum, Oleg Kovalov, Abhijeet Rastogi and 34 more
I love most of what this does, but I don't support its addition as it stands. Specifically, I have issues with the option to use inline key-value pairs in the log calls. I believe the attributes system alone is fine. Logging does not need the breakage that key-value args like that allow.
The complexity in the documentation around Log should be a warning sign.
...
The attribute arguments are processed as follows:
If an argument is an Attr, it is used as is.
If an argument is a string and this is not the last argument, the following argument is treated as the value and the two are combined into an Attr.
Otherwise, the argument is treated as a value with key "!BADKEY".
The suggestion was that potential problems with key-value misalignment will all be solved by vet checks. As I mentioned in this thread of the discussion, relying on vet should be viewed a warning as to potential problems with the design, not a part of the design itself. Vet should not be a catch-all, and we should do what we can to avoid requiring vet warnings to safely develop go.
An accidentally deleted/missed or added/extra argument in key value logging would offset the keys and values after it. That could easily bog down a logging system trying to index all the new "keys" it is getting. It could also lead to data being exposed in a way that it should not be.
I acknowledge that neither of these examples are all that likely in a well defined system, or somewhere with good practices around code reviewing etc.. But they are possible.
Reacted by Justin Nuß, beardo, Ilia Choly, Shane Hanna, Viktor Kojouharov, Mike Trpcic, Komu Wairagu, Tomáš Karela Procházka, Nathan Disidore, Leo Antunes and 35 more
Reacted by Ethan Reesor and Denis
Reacted by Ilia Choly, David Brouwer, Tomáš Karela Procházka, Kevin Loney, Simon Rozet, Benjamen Meyer and Teofanus Gary
Based on that the Go standard library is very often being recommended as source of idiomatic code, and this package aspires to be merged as part of it, I would like you explain to me the involvement of context package.
If some object uses logger, isn't it its dependency? Shouldn't we make the dependencies explicit? Isn't this logger smuggling bad practice? If passing the logger by context is idiomatic, is *sql.DB too?
Why has the logger stored context? It violates the most famous sentence from the documentation for context
Contexts should not be stored inside a struct type, but instead passed to each function that needs it.
Logger in context containing its own context inside ...
Frankly, I'm a bit confused.
Reacted by Mike M, markraymondlush, Alexei Shevchenko, David Brouwer, Ewen Quimerc'h, Chris Hines, Paweł, ansoni-san, Ainar Garipov, Abhijeet Rastogi and 22 more
Reacted by Chris Hines, J. Emrys Landivar and Bradley Wilson-Hunt
@hherman1 The same concern does apply to printf, tho it's not as bad compared to logging. With printf, most statements are consumed as a single chunk, and only consumed locally by the programmer. Being misaligned is easy enough for a human to notice, parse, and correct.
In the case of Sprintf, where it might not be consumed by the programmer, and instead be used as an argument to something, the "testing" during development that is making sure the program starts would likely catch most misalignment.
Being off by one in a log is much harder to catch as it has no real impact in the program's execution. You only notice there is an issue when you have to go through your logs.
Reacted by Ilia Choly, Viktor Kojouharov, David Brouwer, Karol Marcjan, Will Faught, Robert Ford, antichris, Rafael, Makpoc, pablochacin and 3 more
I think I share some of @prochac 's concerns regarding context. Maybe I'm being a bit of a luddite, but recommending that the logger is passed around inside context rather than via explicit dependency injection, smells a bit funny to me. Context, from what I have always followed, is for request-scoped information, rather than dependencies. And the more clarity surfacing dependencies the better. IE just assuming the right logger is in context, and getting the default one because it's still getting some logger
Reacted by Tomáš Karela Procházka, Paweł, maja42, Simon Rozet, Will Faught, Luke Stoward, Simon Mikkelsen, 0000 1000 1101 0010, Robert Ford, Linus Jakobsson and 8 more
It seems that NewContext(...) and FromContext(...), are not in Go 1.21, which are in the discussion. But I think that they're really useful. I wonder that will they be supported in the future?
Storing and retrieving a slog.Logger is not ideal because storing it must convert to logr.Logger and retrieving must undo that, which causes additional memory allocations. I have an idea how that could be avoided, but for that the main logr package must depend on slog, which is something that @thockin and I wanted to avoid until there is specific demand for it.
github.com/veqryn/slog-context has support for two different workflows for using slog and context: storing and retrieving the logger from Context and/or storing and retrieving attributes and values from Context. (These workflows can be used individually or together at the same time.)
Attributes Extracted from Context Workflow:
Using the slogctx.Handler lets you Prepend and Append attributes to log lines, even when a logger is not passed into a function or in code we don't control. This is done without storing the logger in the context; instead the attributes are stored in the context and the Handler picks them up later whenever a new log line is written.
In that same workflow, the HandlerOptions and AttrExtractor types let you extract any custom values from a context and have them automatically be prepended or appended to all log lines using that context.
For example, the slogotel.ExtractTraceSpanID extractor will automatically extract the OTEL (OpenTelemetry) TraceID and SpanID, and add them to the log record, while also annotating the Span with an error code if the log is at error level.
Logger in Context Workflow:
Using NewCtx and FromCtx let you store the logger itself within a context, and get it back out again. Wrapper methods With/WithGroup/Debug/Info/Warn/Error/Log/LogAttrs let you work directly with a logger residing with the context (or the default logger if no logger is stored in the context).
Using the slogcontext.Handler lets you Prepend and Append attributes to log lines, even when a logger is not passed into a function or in code we don't control. This is done without storing the logger in the context; instead the attributes are stored in the context and the Handler picks them up later whenever a new log line is written.
This only works when the code "that you don't control" is careful about always passing the context into any log calls that it invokes, right? So it won't work for e.g. slog.Info, which is what developers who don't care (or don't know) about logging values from context might pick. It also breaks once you need to call code which accepts a Logger, but not a context.
github.com/veqryn/slog-context: [...] storing and retrieving the logger from Context
This won't be interoperable with Kubernetes packages, which use go-logr/logr for this (wrapped by klog, but that's an implementation detail).
My point is: without a convention that is followed by all reusable Go packages, these mechanisms work in scenarios where one controls the entire code base, but not when combining code from different sources.
Maybe it could be worth it adding to slogr something similar to the following:
// NewContext from ctx with a logger that uses [slog.Handler] from the// provided [slog.Logger].// An instance of [slog.Logger] using the handler can be retrieved from// this context using [FromContext].funcNewContext(ctx context.Context, l*slog.Logger) context.Context {
returnlogr.NewContext(ctx, NewLogr(l.Handler()))
}
// FromContext returns an [slog.Logger] from the provided context.funcFromContext(ctx context.Context) (l*slog.Logger) {
returnslog.New(NewSlogHandler(logr.FromContextOrDiscard(ctx)))
}
That allocation of a new slog.Logger, 16 bytes on a 64-bit system, on every retrieval is unfortunate, though.
@antichris: We had considered that and decided against it at the time because it's just syntactic sugar and not necessarily a good solution because of that additional allocation. That constructing a Logger needs an allocation is unfortunate for cases like this where an instance needs to be created frequently. I had suggested to pass slog.Logger by value to avoid it, but that wasn't accepted.
But perhaps that's okay. I think it's fine to simply use the logr.Logger API on top of a slog.Handler via slogr - that's not that different from using slog.Logger as higher-level API. So if someone wants to accept a logger via a context, just use logr? That's what I am proposing for Kubernetes, with some additional support for slog. It avoids further code churn and I just couldn't find a good reason that would have justified switching.
I'm not sure whether this issue is the right place to discuss this, though. I've created go-logr/logr#234 for further discussion and tracking.
in the (initial proposal) it was part of the plan but it could cause some performance issue.
There should be a way to consider correlation like, like trace_id, I believe storing the whole logger object is not the best way, maybe just storing fields in the logger could be an option for consideration?
slog as an API already supports "store and extract values":
Ensure that all log calls have a context and use the *Ctx variant of the slog.Logger API.
Use a slog.Handler which supports extracting the values that you are interested in. Third-party libraries often hide their context key behind their API, so such a handler must be able to call the API of those libraries, or you need to write a wrapper slog.Handler which does this.
Regarding performance: this depends on usage patterns. If all call chains have additional values, then extracting those is going to be faster. If you have many different values and each call chain only has one of those values, then checking for all of them on each log call is probably slower.
Hello, everyone! I've been trying out slog and hit some bumps with how the API is designed. Essentially, for my use case I need to check a certain log level and dispatch an alert, and still log normally. A middleware for the default handler basically. Should be something very straight forward, but in the current state, there's no (proper) way to wrap the default handler.
The handlers that slog allows to be constructed don't log in the pretty TIME LEVEL Msg Key=Pair format, essentially forcing me to reimplement the *commonHandler (or a custom one).
Problem
I tried a different approach but hit another roadblock.
From what I've gathered, it seems to be because of what is being done in SetDefault
funcSetDefault(l*Logger) {
// ...// This bit hereif_, ok:=l.Handler().(*defaultHandler); !ok {
capturePC:=log.Flags()&(log.Lshortfile|log.Llongfile) !=0log.SetOutput(&handlerWriter{l.Handler(), LevelInfo, capturePC})
log.SetFlags(0) // we want just the log message, no time or location
}
}
If I got this right, this was added for compatibility with log but... it smells like bad practice to me. IMO this should NOT be done by SetDefault (a separate function for that), or defaultLogger should be exported. Another option would be a SetDefaultHandler, or a With(slog.Handler)/With(*slog.Logger) similar to how WithGroup works, but for a chain of loggers.
Workaround
If I were to do the following, which feels like a very very bad thing to do, it now works.
typeWrapperHandlerstruct {
slog.Handler
}
func (w*WrapperHandler) Handle(ctx context.Context, record slog.Record) error {
doSomething()
returnw.Handler.Handle(ctx, record)
}
funcmain() {
wrap:=&WrapperHandler(slog.Default().Handler())
*slog.Default() =*slog.New(wrap) // Bypass SetDefault using pointer hacks (not thread-safe obviously)slog.Info("TEST") // Works just fine
}
Remarks
I'm sorry if these things have been answered already, or if I'm overlooking something, I read the entire documentation and looked through the thread and didn't find this being mentioned. I can't really wrap (pun intended) my head around why extending/wrapping the default logger is so obtuse. I'm aware I could build my own logging package around slog, or use one of the great existing ones, but considering how feature complete slog looks I would rather stick with it as much as possible.
Edit: After some more digging I found this to be related to #62418 and #61892
Activity
jba commentedon Oct 20, 2022
fsouza commentedon Oct 20, 2022
jba commentedon Oct 20, 2022
mpx commentedon Oct 22, 2022
This is a huge API surface without any real production testing (AIUI). Perhaps it might be better to land it under golang.org/x for some time? Eg, like context, xerrors changes.
seankhliao commentedon Oct 22, 2022
It's available under golang.org/x/exp/slog
mpx commentedon Oct 22, 2022
deefdragon commentedon Oct 24, 2022
I love most of what this does, but I don't support its addition as it stands. Specifically, I have issues with the option to use inline key-value pairs in the log calls. I believe the attributes system alone is fine. Logging does not need the breakage that key-value args like that allow.
The complexity in the documentation around Log should be a warning sign.
The suggestion was that potential problems with key-value misalignment will all be solved by vet checks. As I mentioned in this thread of the discussion, relying on vet should be viewed a warning as to potential problems with the design, not a part of the design itself. Vet should not be a catch-all, and we should do what we can to avoid requiring vet warnings to safely develop go.
An accidentally deleted/missed or added/extra argument in key value logging would offset the keys and values after it. That could easily bog down a logging system trying to index all the new "keys" it is getting. It could also lead to data being exposed in a way that it should not be.
I acknowledge that neither of these examples are all that likely in a well defined system, or somewhere with good practices around code reviewing etc.. But they are possible.
hherman1 commentedon Oct 24, 2022
@deefdragon Doesn't this concern apply to Printf as well? Is the difference the dependency on these logs by external systems..?
prochac commentedon Oct 24, 2022
Based on that the Go standard library is very often being recommended as source of idiomatic code, and this package aspires to be merged as part of it, I would like you explain to me the involvement of context package.
If some object uses logger, isn't it its dependency? Shouldn't we make the dependencies explicit? Isn't this logger smuggling bad practice? If passing the logger by context is idiomatic, is *sql.DB too?
Why has the logger stored context? It violates the most famous sentence from the documentation for context
Logger in context containing its own context inside ...
Frankly, I'm a bit confused.
deefdragon commentedon Oct 24, 2022
@hherman1 The same concern does apply to printf, tho it's not as bad compared to logging. With printf, most statements are consumed as a single chunk, and only consumed locally by the programmer. Being misaligned is easy enough for a human to notice, parse, and correct.
In the case of Sprintf, where it might not be consumed by the programmer, and instead be used as an argument to something, the "testing" during development that is making sure the program starts would likely catch most misalignment.
Being off by one in a log is much harder to catch as it has no real impact in the program's execution. You only notice there is an issue when you have to go through your logs.
mminklet commentedon Oct 25, 2022
I think I share some of @prochac 's concerns regarding context. Maybe I'm being a bit of a luddite, but recommending that the logger is passed around inside context rather than via explicit dependency injection, smells a bit funny to me. Context, from what I have always followed, is for request-scoped information, rather than dependencies. And the more clarity surfacing dependencies the better. IE just assuming the right logger is in context, and getting the default one because it's still getting some logger
869 remaining items
tzq0301 commentedon Nov 16, 2023
It seems that
NewContext(...)andFromContext(...), are not in Go 1.21, which are in the discussion. But I think that they're really useful. I wonder that will they be supported in the future?jba commentedon Nov 16, 2023
They were very controversial. I don't see that changing.
pohly commentedon Nov 17, 2023
https://pkg.go.dev/github.com/go-logr/logr has
NewContextandFromContext. In 1.3.0 (released last month) I added support for slog (see "slog interoperability" in the README and https://pkg.go.dev/github.com/go-logr/logr@v1.3.0/slogr), so now you can do:Storing and retrieving a
slog.Loggeris not ideal because storing it must convert tologr.Loggerand retrieving must undo that, which causes additional memory allocations. I have an idea how that could be avoided, but for that the main logr package must depend on slog, which is something that@thockinand I wanted to avoid until there is specific demand for it.veqryn commentedon Nov 18, 2023
github.com/veqryn/slog-context has support for two different workflows for using slog and context: storing and retrieving the logger from Context and/or storing and retrieving attributes and values from Context. (These workflows can be used individually or together at the same time.)
Attributes Extracted from Context Workflow:
Using the
slogctx.Handlerlets youPrependandAppendattributes to log lines, even when a logger is not passed into a function or in code we don't control. This is done without storing the logger in the context; instead the attributes are stored in the context and the Handler picks them up later whenever a new log line is written.In that same workflow, the
HandlerOptionsandAttrExtractortypes let you extract any custom values from a context and have them automatically be prepended or appended to all log lines using that context.For example, the
slogotel.ExtractTraceSpanIDextractor will automatically extract the OTEL (OpenTelemetry) TraceID and SpanID, and add them to the log record, while also annotating the Span with an error code if the log is at error level.Logger in Context Workflow:
Using
NewCtxandFromCtxlet you store the logger itself within a context, and get it back out again. Wrapper methodsWith/WithGroup/Debug/Info/Warn/Error/Log/LogAttrslet you work directly with a logger residing with the context (or the default logger if no logger is stored in the context).@tzq0301 @andviro @lpar @pohly
pohly commentedon Nov 18, 2023
This only works when the code "that you don't control" is careful about always passing the context into any log calls that it invokes, right? So it won't work for e.g.
slog.Info, which is what developers who don't care (or don't know) about logging values from context might pick. It also breaks once you need to call code which accepts aLogger, but not a context.This won't be interoperable with Kubernetes packages, which use go-logr/logr for this (wrapped by klog, but that's an implementation detail).
My point is: without a convention that is followed by all reusable Go packages, these mechanisms work in scenarios where one controls the entire code base, but not when combining code from different sources.
antichris commentedon Nov 18, 2023
@pohly
Maybe it could be worth it adding to
slogrsomething similar to the following:That allocation of a new
slog.Logger, 16 bytes on a 64-bit system, on every retrieval is unfortunate, though.pohly commentedon Nov 18, 2023
@antichris: We had considered that and decided against it at the time because it's just syntactic sugar and not necessarily a good solution because of that additional allocation. That constructing a
Loggerneeds an allocation is unfortunate for cases like this where an instance needs to be created frequently. I had suggested to passslog.Loggerby value to avoid it, but that wasn't accepted.But perhaps that's okay. I think it's fine to simply use the
logr.LoggerAPI on top of aslog.Handlerviaslogr- that's not that different from usingslog.Loggeras higher-level API. So if someone wants to accept a logger via a context, just uselogr? That's what I am proposing for Kubernetes, with some additional support for slog. It avoids further code churn and I just couldn't find a good reason that would have justified switching.I'm not sure whether this issue is the right place to discuss this, though. I've created go-logr/logr#234 for further discussion and tracking.
yuseferi commentedon Nov 21, 2023
in the (initial proposal) it was part of the plan but it could cause some performance issue.
There should be a way to consider correlation like, like
trace_id, I believe storing the whole logger object is not the best way, maybe just storing fields in the logger could be an option for consideration?yuseferi commentedon Dec 24, 2023
I did a benchmark here on zax and it shows storing fields instead of the whole logger object gives better performance :
pohly commentedon Dec 25, 2023
slogas an API already supports "store and extract values":*Ctxvariant of theslog.LoggerAPI.slog.Handlerwhich supports extracting the values that you are interested in. Third-party libraries often hide their context key behind their API, so such a handler must be able to call the API of those libraries, or you need to write a wrapperslog.Handlerwhich does this.Regarding performance: this depends on usage patterns. If all call chains have additional values, then extracting those is going to be faster. If you have many different values and each call chain only has one of those values, then checking for all of them on each log call is probably slower.
veqryn commentedon Jan 3, 2024
@pohly and I talked a bit and agreed to coordinate our repos.
So I am happy to announce that github.com/veqryn/slog-context now supports both stdlib
slogas well as github.com/go-logr/logr at the same time.You can use
github.com/veqryn/slog-contextto:*slog.Loggerin thecontext.Context, and either get it back out again or work with it directly in the ctx.Context(useful for adding attributes/baggage to log lines in libraries).Contextand add them to all log lines using that context.Minoxs commentedon Jan 4, 2024
Hello, everyone! I've been trying out slog and hit some bumps with how the API is designed. Essentially, for my use case I need to check a certain log level and dispatch an alert, and still log normally. A middleware for the default handler basically. Should be something very straight forward, but in the current state, there's no (proper) way to wrap the default handler.
The handlers that slog allows to be constructed don't log in the pretty
TIME LEVEL Msg Key=Pairformat, essentially forcing me to reimplement the*commonHandler(or a custom one).Problem
I tried a different approach but hit another roadblock.
From what I've gathered, it seems to be because of what is being done in
SetDefaultIf I got this right, this was added for compatibility with log but... it smells like bad practice to me. IMO this should NOT be done by SetDefault (a separate function for that), or
defaultLoggershould be exported. Another option would be a SetDefaultHandler, or a With(slog.Handler)/With(*slog.Logger) similar to how WithGroup works, but for a chain of loggers.Workaround
If I were to do the following, which feels like a very very bad thing to do, it now works.
Remarks
I'm sorry if these things have been answered already, or if I'm overlooking something, I read the entire documentation and looked through the thread and didn't find this being mentioned. I can't really wrap (pun intended) my head around why extending/wrapping the default logger is so obtuse. I'm aware I could build my own logging package around slog, or use one of the great existing ones, but considering how feature complete slog looks I would rather stick with it as much as possible.
Edit: After some more digging I found this to be related to #62418 and #61892