Skip to content

Supported way of accumulating metadata down the callstack #261

Open
@weissi

Description

@weissi

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/enhancementImprovements to existing feature.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions