Description
Description
I need a way where multiple, independent libraries can accumulate metadata down the callstack.
So for a call stack looking like
LibA.someFunc
-> LibB.anotherFunc
-> LibC.yetAnotherOne
-> LibD.loggingFunction
I want LibA.someFunc
, LibB.anotherFunc
and LibC.yetAnotherOne
to be able to accumulate metadata keys that are attached to messages logged by LibD.loggingFunction
.
For example if LibD.loggingFunction
calls logger.info("hello world")
, I would expect the following log message:
DATETIME INFO hello world ["LibA-Key": "A", "LibB-Key": "B", "LibC-Key": "C"]
assuming that LibA.someFunc
added LibA-Key
and so on.
Given that we now have Baggage
and TaskLocals, I would expect this to work without having to pass Logger
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 a Logger
in a TaskLocal; or a Baggage
in a TaskLocal where the Baggage
has a well-known baggage.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
func foo(logger: Logger) async throws {
var logger = logger
logger[metadataKey: "foo"] = "\(UUID())"
for f in 0..<10 {
try await bar(logger: logger)
}
}
func bar(logger: Logger) async throws {
var logger = logger
logger[metadataKey: "bar"] = "\(UUID())"
logger.logLevel = .trace
for f in 0..<10 {
try await httpClient.get("https://foo.bar/buz", logger: logger)
}
}
// main
let logger = Logger(label: "my-service")
try await foo(logger: logger)
But please note that I have to manually thread through the Logger
.