-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Supported way of accumulating metadata down the callstack #261
Comments
What do you think about adding just the metadata to the task local but still threading the logger itself around. This still reduces a bunch of logger passing to individual methods where you can just pass a logger to the We could then add a build in metadata provider that pulls the metadata from this task local. I am a bit unsure if stuffing the logger itself into the task local is the right thing to do because it then suddenly becomes like a dependency injection system. Furthermore, what do you do if the task local is not set because you jumped in and out of structured concurrency? IMO, I would love to explore the metadata in task local first and see how that evolves before we stuff the whole logger in there. Side note: |
Better than nothing but still incomplete IMHO. That won't allow me to change the log level for known problematic areas of the code. It's often useful to say lower the log-level for a given HTTP route or maybe log every 10,000th request in
TaskLocals are a dependency injection system. They allow you to essentially pass invisible parameters down the call stack.
Yes, that's actually a really good idea. Most logging sites don't need to change or know anything about the logger, they just want to log something, having static methods makes this rather nice. |
I have been looking at this as well for one of our services. We solved it with a log handler that allows to set the log level dynamically during runtime on a per file basis. I quite like the approach of log handlers doing this instead of doing this at the places of logging.
Sure, but it is a bit of a fragile system if you step into unstructured concurrency etc. What would you do if there is no logger in the task local? |
That's orthogonal. That sets everything to a different level, I want just want to change it for everything down from the current call stack. Not sure what you mean on a per file basis. But in general,
Yes, of course, all implicit systems are fragile (but handy; mind you totally valid opinion to say that you don't like implicit systems and avoid them). But to your question: I think the answer to this one is pretty straightforward: You fall back to a logger that doesn't have any contextual information attached to it. The only context is that we don't have the context. The user is either fine with that in which case this works or the user isn't in which case they should fix their code (either stay in structured concurrency or transport the Logger over). I'd say a global
or something similar will do the job just fine. |
To expand on this a bit. We have a log handler that has a dynamic log level configuration for each file. The log handler parses the
I do like implicit dependency injection systems when they come with compile safety. This often requires code analysis in a build phase, so I think this is out of scope here. However, it looks like this is the thing you want to achieve here the most. Being able to update the log level dynamically from some point on downwards. I am wondering if this might be better solved with something like the above or how log4j handles this. You most likely want to change the log level for debugging crashes/weird behaviours. That is done on an application level mostly so if we can provide a configuration to the top level logger that is injected all the way down we can achieve this as well. Especially thinking about applications that depend on things like gRPC or AHC. If they want to change the log levels of some of the logs inside these libraries the task local logger approach won't help them at all because they have no control over the libs. |
Right, this is orthogonal. I want everything from a certain point onwards down the call stack, even if in a completely different library. For example, assume you have these call stacks:
in Similarly if for example I want to say (in |
In SLF4J and Log4j 1.x this is called MDC (Mapped Diagnostics Context), I think in Log4j 2.x it's called ThreadContext. Most Java libraries assume that everything is synchronous and if you have a primitive that crosses threads, then I think you're meant to detach and re-attach the MDC context accordingly. But from a logging perspective, Java's threads locals are what Swift Concurrency's task locals are to us. MDC's context is more like our metadata but because the Java logging systems pull much more into the API than SwiftLog, you can achieve what I want to do there using the XML config files. Quickest example I could find is this: https://ravthiru.medium.com/dynamically-set-per-request-log-level-using-logback-filters-d1009e3e11fb I think that's a hack because it requires the application/library and the configuration to cooperate. When we designed SwiftLog, we deliberately made what is called the But really |
Thinking about this some. Overall, I agree we should just lean into making the logger be a task local. This is overall good for the ecosystem since we force people to use structured concurrency. The problem that I was describing upthread is not solved by this but I think it is orthogonal anyhow. We want to be able to dynamically update the log level of all loggers in a running process without restarting. Furthermore, we actually want to do this on a per file basis. We can make this work right now by just having a custom log handler and setting the root logger's log level to |
Sounds all good. Yes, what you describe also needs to be supported and it is. I think it's okay that it's not part of literally |
Description
I need a way where multiple, independent libraries can accumulate metadata down the callstack.
So for a call stack looking like
I want
LibA.someFunc
,LibB.anotherFunc
andLibC.yetAnotherOne
to be able to accumulate metadata keys that are attached to messages logged byLibD.loggingFunction
.For example if
LibD.loggingFunction
callslogger.info("hello world")
, I would expect the following log message:assuming that
LibA.someFunc
addedLibA-Key
and so on.Given that we now have
Baggage
and TaskLocals, I would expect this to work without having to passLogger
down the stack manually.Concrete ask
I propose to create a standard way where all programs & libraries can retrieve and modify a
Logger
from "the environment" (deliberately vague here). For example, that could be aLogger
in a TaskLocal; or aBaggage
in a TaskLocal where theBaggage
has a well-knownbaggage.logger
key; or something entirely different.The important bottom line is that each function can add/modify metadata values as well as other properties (such as logLevel) of the Logger.
Why are MetadataProviders not enough?
Metadata providers require (at the
MetadataProvider
creation time) to know all the metadata that needs to be coalesced. That cannot in general because it's impossible to determine which libraries might get called just to fish out their own MetadataProviders.Current, working solution (manually passing logger)
This all works totally fine today in this example program
But please note that I have to manually thread through the
Logger
.The text was updated successfully, but these errors were encountered: