Skip to content
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

Metadata Providers (e.g. for Distributed Tracing) in LogHandlers #238

Merged
merged 60 commits into from
Jan 18, 2023

Conversation

ktoso
Copy link
Member

@ktoso ktoso commented Nov 24, 2022

This is the third, and hopefully final, revision of MetadataProvider. I believe this achieves everything we could come up with in the multiple rounds of reviews and calls, and introduces zero dependencies or api breaks to the project.

Thank you to @slashmo, the NIO and SSWG teams for the continued discussions 👍

This approach is summarized as:

  • log handlers gain metadata providers
  • providers, and whole of logging, remain unaware of Baggage
  • a provider implementation, e.g. metadataProvider: .otel can and will pick up baggage from task-locals, since they do depend on baggage and tracing APIs.
  • swift-distributed-tracing is able to implement provideMetadata(from: Baggage)

Updated proposal text: https://github.com/ktoso/swift-log/blob/wip-baggage-in-handlers-but-not-api/proposals/0001-metadata-providers.md


Distributed tracing is able to provide the following integration, in the Instrumentation module:

#if swift(>=5.5.0) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Logger {

    /// Extract any potential ``Logger/Metadata`` that might be obtained by extracting the
    /// passed ``InstrumentationBaggage/Baggage`` to this logger's configured ``Logger/MetadataProvider``.
    ///
    /// Use this when it is necessary to "materialize" contextual baggage metadata into the logger, for future use,
    /// and you cannot rely on using the task-local way of passing Baggage around, e.g. when the logger will be
    /// used from multiple callbacks, and it would be troublesome to have to restore the task-local baggage in every callback.
    ///
    /// Generally prefer to set the task-local baggage using `Baggage.current` or `Tracer.withSpan`.
    public mutating func provideMetadata(from baggage: Baggage?) {
        guard let baggage = baggage else {
            return
        }

        Baggage.withValue(baggage) {
            let metadata = self.metadataProvider.provideMetadata()
            for (k, v) in metadata {
                self[metadataKey: k] = v
            }
        }
    }
}
#endif

which allows us to "capture all baggage into metadata ONCE" and then keep using this logger with the metadata; which was a feature request from NIO-heavy libraries.


The API break reported is:

22:45:48 /* Protocol Requirement Change */
22:45:48 Var LogHandler.metadataProvider has been added as a protocol requirement
22:45:48 

which is accounted for by a default implementation:

public extension LogHandler {
    /// Default implementation for `metadataProvider` which defaults to a "no-op" provider.
    var metadataProvider: Logger.MetadataProvider {
        get {
            return .noop
        }
        set {
            self.log(level: .warning, message: "Attempted to set metadataProvider on \(Self.self) that did not implement support for them. Please contact the log handler maintainer to implement metadata provider support.", metadata: nil, source: "Logging", file: #file, function: #function, line: #line)
        }
    }
}

and this will never be triggered unless someone very actively in their end user library tries to make use of this, and then they'll get this warning. Existing libraries continue to work, but SHOULD implement support for invoking the metadata providers as otherwise the metadata will not be included in log statements. They can do so at their own pace though.


Since I wanted to include tests showing how task-locals are to be used with this, but Swift 5.0 cannot lex these properly even (confirmed this compiler bug with the compiler team; this will not be fixed), I had to make a new module for them.

This was quite painful, so either we keep this hacky separate module, or we drop those tests, or we drop 5.0 support. Either is fine with me.

@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch 2 times, most recently from d4805c3 to 80133fc Compare November 24, 2022 10:40
@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch 2 times, most recently from 8e1ba53 to 3d53099 Compare November 24, 2022 10:49
@@ -873,18 +920,20 @@ class LoggingTest: XCTestCase {
}
}

extension Logger {
public extension Logger {
Copy link
Member Author

@ktoso ktoso Nov 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mismatched format versions truly are fun... Going to fix all those

@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch 3 times, most recently from 4c543ab to 45f506c Compare November 24, 2022 13:53
@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch 2 times, most recently from d9582e2 to 92fc671 Compare November 24, 2022 14:24
end

false
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is horrible, though good enough to have the discussion if we want to make this workaround less horrible, remove those few tests entirely (and remove this hack), or what else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this working around?

Copy link
Member Author

@ktoso ktoso Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bug in Swift 5.0 that it cannot parse/lex code that has the shape of "Thing.$thing", which task-locals have to use - even when surrounded with #if swift(>=5.1) swift 5.0 will still fail to compile such code (yeah, it should ignore code in that #if, but it doesn't).

Options have are:

  • don't have any tests which show how a task-local can be used with providers (we could not have those tests... and just static providers everywhere in our tests, though it's a bit annoying)
    • (no actual code is 5.0 breaking actually in this PR, it is only the tests)
  • or use this separate module workaround

We can still have testing of this pattern in the tracing module, but it's a bit weird to test metadata provider patterns in a different package than the one declaring them.

I talked with the Swift team a bunch about this, there is no workaround in Swift 5.0 (and there will not be).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems most reviewers voted for dropping 5.0, so I did so and undid this hack workaround: ad2fd8f (#238)

Copy link
Member

@FranzBusch FranzBusch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just read through all of this again and I really like where we ended up now. It looks like an organic extension to swift-log which plays nicely with distributed tracing!
I left a few small comments inline but overall big +1!

W.r.r. the required hack, I would vote for increasing the min Swift version. NIO is setting precedent here already and IMO it should be fine to have the same rules for swift-log.

#endif

#if compiler(>=5.6)
@preconcurrency public protocol _SwiftLogSendable: Sendable {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly these don't even need to be public to work. Might be wrong though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I'll check but I think they need to be public -- it's for other libs to know this type is Sendable after all 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly this also works with an internal protocol. Maybe this changed but I tried to for one of our NIO repos. Somehow the compiler still transfers the Sendable constraint 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested this again. Since Sendable is only a marker protocol you don't have to make this public. It works without as well and propagates across modules.

}

#if swift(>=5.5) && canImport(_Concurrency)
public typealias Function = @Sendable() -> Metadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I am not a fan of using typealiases here. They are hard to discover.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 against function typealiases in Swift, super hard to work with, especially with Xcode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, I'll have to re-declare a bunch of entire function bodies unless I do a typealias like that -- so pretty painful tradeoff

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep painful but better end user experience 😅

Sources/Logging/MetadataProvider.swift Outdated Show resolved Hide resolved
Sources/Logging/MetadataProvider.swift Outdated Show resolved Hide resolved
Package.swift Outdated
@@ -32,5 +32,16 @@ let package = Package(
name: "LoggingTests",
dependencies: ["Logging"]
),
// Due to a compiler bug in parsing/lexing in 5.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote that we drop 5.0 support

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, honestly it's about time. We can go careful and just drop 5.0, the others we can keep around still.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, removed the horrible workaround with modules;
We'll have to drop 5.0 though.

Sources/Logging/Locks.swift Outdated Show resolved Hide resolved
)
}

func makeWithMetadataProvider(label: String, metadataProvider: Logger.MetadataProvider) -> LogHandler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collapse this into make and make metadataProvider optional with default of .none?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That'll cause a series of ambiguity errors AFAIR, I'll give it a shot tho.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'll push back on this one, it causes a lot more pain in our test code than necessary;
We can't do LoggingSystem.bootstrapInternal(logging.makeWithMetadataProvider) style anymore and would have to spell out all the closures to disambiguate.

Note that this is only our test code, so the verbose name is not a concern IMHO

@@ -43,13 +43,19 @@ public struct Logger {
/// An identifier of the creator of this `Logger`.
public let label: String

/// The metadata provider this logger was created with.
/// If no provider was configured, this will return a ``Logger/MetadataProvider/noop`` no-op metadata provider.
public var metadataProvider: Logger.MetadataProvider {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to be public?

Copy link
Member Author

@ktoso ktoso Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is public on purpose in order to facilitate external libraries, such as tracing, to implement the "provideMetadata" functions, like this:

#if swift(>=5.5.0) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Logger {

    /// Extract any potential ``Logger/Metadata`` that might be obtained by extracting the
    /// passed ``InstrumentationBaggage/Baggage`` to this logger's configured ``Logger/MetadataProvider``.
    ///
    /// Use this when it is necessary to "materialize" contextual baggage metadata into the logger, for future use,
    /// and you cannot rely on using the task-local way of passing Baggage around, e.g. when the logger will be
    /// used from multiple callbacks, and it would be troublesome to have to restore the task-local baggage in every callback.
    ///
    /// Generally prefer to set the task-local baggage using `Baggage.current` or `Tracer.withSpan`.
    public mutating func provideMetadata(from baggage: Baggage?) {
        guard let baggage = baggage else {
            return
        }

        Baggage.withValue(baggage) {
            let metadata = self.metadataProvider.provideMetadata()
            for (k, v) in metadata {
                self[metadataKey: k] = v
            }
        }
    }
}
#endif

self._factory.replaceFactory(factory, validate: false)
}

fileprivate static var factory: (String) -> LogHandler {
return self._factory.underlying
internal static func bootstrapInternal(metadataProvider: Logger.MetadataProvider) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bootstrapInternalMetadataProvider? or maybe collapse the two internal bootstrap functions to one, with metadata provider optional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped it entirely, we only have those ONE new shape of bootstraps:

    internal static func bootstrapInternal(_ factory: @escaping (String, Logger.MetadataProvider?) -> LogHandler,
                                           metadataProvider: Logger.MetadataProvider?) {
    public static func bootstrap(_ factory: @escaping (String, Logger.MetadataProvider?) -> LogHandler,
                                 metadataProvider: Logger.MetadataProvider?) {

/// - parameters:
/// - metadataProvider: The `MetadataProvider` used to inject runtime-generated metadata, defaults to a "no-op" provider.
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
static func bootstrap(metadataProvider: Logger.MetadataProvider,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like the metadataProvider should be the second argument

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A while ago we came to the order of parameters recommendations around "first strict, then closures, then optional params, then optional closures" so this follows that recommendation.

I'm not sure how many people actually write

LoggingSystem.bootstrap {
  many
  lines
  return here
}

so it was intended to keep that shape 🤔

LoggingSystem.bootstrap(metadataProvider: .coolTracer) {
  many
  lines
  return here
}

if we flip it, we end up with:

LoggingSystem.bootstrap({
  many
  lines
  return here
}, metadataProvider: .coolTracer)

potentially... but arguably most uses do look like this:

LoggingSystem.bootstrap(.coolLogger, metadataProvider: .coolTracer)

potentially... so, I'm not really sure about this one. The current shape kind of follows the "usual" order, with closures last. Open to either but current seemed more natural given the usage shapes hm

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, let's do it 😄

})
}

init(label: String, metadataProvider: @escaping MetadataProvider.Function) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could auto-closure be useful here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, yeah we could I guess; it'd allow a simple spelling for a "constant metadata provider" that is just the dictionary

@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch from f7dd9cd to 91437c0 Compare January 14, 2023 02:53
@ktoso
Copy link
Member Author

ktoso commented Jan 14, 2023

Followed up on your comments in 91437c0 @tomerd -- the reordering of the params I'm doubtful about, see here: #238 (comment)

but I could flip those around still... that's the final thing i guess?

@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch from d798dcb to 33e0316 Compare January 18, 2023 06:52
///
/// - parameters:
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
public static func bootstrap(_ factory: @escaping (String) -> LogHandler) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this just moved "up" a little in the file, to be organized together with the other ones

@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch from 33e0316 to 3d8e543 Compare January 18, 2023 06:54
@ktoso ktoso force-pushed the wip-baggage-in-handlers-but-not-api branch from 514e8e0 to 561c5aa Compare January 18, 2023 07:11
@@ -1090,19 +1206,21 @@ public struct StreamLogHandler: LogHandler {

/// Factory that makes a `StreamLogHandler` to directs its output to `stdout`
public static func standardOutput(label: String) -> StreamLogHandler {
return StreamLogHandler(label: label, stream: StdioOutputStream.stdout)
return StreamLogHandler(label: label, stream: StdioOutputStream.stdout, metadataProvider: LoggingSystem.metadataProvider)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default impls "do the right thing"™

@ktoso
Copy link
Member Author

ktoso commented Jan 18, 2023

Discussed offline with @tomerd and I did another pass and removed as much new public API as possible.

We're done! I'm going to merge and release a minor version with this. This will cause an all our tracing repos to "fall into place" and we're soon going to 1.0-beta there 🥳

@ktoso
Copy link
Member Author

ktoso commented Jan 18, 2023

To make sure folks don't panic: The API breakage detector is wrong here; we add a protocol requirement, but we add a default impl as well, this is source-compatible and purely additive.

@ktoso ktoso changed the title RFC (rev 3): Metadata Providers (for Distributed Tracing) in LogHandlers in swift-log Metadata Providers (e.g. for Distributed Tracing) in LogHandlers Jan 18, 2023
@ktoso ktoso merged commit e6ca651 into apple:main Jan 18, 2023
@ktoso ktoso added this to the 1.5.0 milestone Jan 18, 2023
renovate bot added a commit to cgrindel/rules_swift_package_manager that referenced this pull request Mar 30, 2023
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [apple/swift-log](https://github.com/apple/swift-log) | minor |
`from: "1.4.4"` -> `from: "1.5.2"` |

---

### Release Notes

<details>
<summary>apple/swift-log</summary>

### [`v1.5.2`](https://github.com/apple/swift-log/releases/tag/1.5.2)

[Compare
Source](https://github.com/apple/swift-log/compare/1.5.1...1.5.2)

#### Primary change

Address too aggressive warning logging on LogHandlers that do not
support `MetadataProvider`. The warning would be emitted too frequently,
resulting in flooding logs with warnings. Instead, the warning is now
emitted once per log handler type.

#### What's Changed

- Avoid logging warnings when handler does not support metadataproviders
by [@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#252
- Handle providers properly in multiplex log handler by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#254
- Add CI for Swift 5.8 and update nightly to Ubuntu 22.04 by
[@&#8203;yim-lee](https://github.com/yim-lee) in
[apple/swift-log#255

**Full Changelog**:
apple/swift-log@1.5.1...1.5.2

### [`v1.5.1`](https://github.com/apple/swift-log/releases/tag/1.5.1)

[Compare
Source](https://github.com/apple/swift-log/compare/1.5.0...1.5.1)

#### Summary

This patch release focuses on minor cleanups to ergonomics of setting
metadata providers with the default stream log handlers, and fixes a bug
in the default handler not printing the provided extra metadata by
default (it does now).

Thank you to [@&#8203;slashmo](https://github.com/slashmo) for quickly
noticing and providing a patch for the latter!

#### What's Changed

- Allow passing explicit provider into the stream handlers by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#250
- Emit correct metadata from StreamLogHandler by
[@&#8203;slashmo](https://github.com/slashmo) in
[apple/swift-log#251

**Full Changelog**:
apple/swift-log@1.5.0...1.5.1

### [`v1.5.0`](https://github.com/apple/swift-log/releases/tag/1.5.0)

[Compare
Source](https://github.com/apple/swift-log/compare/1.4.4...1.5.0)

### Changes

#### Swift version support

This release drops support for Swift 5.0.

Swift 5.1+ remain supported for the time being.

#### Logger.MetadataProvider

This release introduces metadata providers!

They are an additional way to add metadata to your log statements
automatically whenever a log statement is about to be made. This works
extremely well with systems like distributed tracing, that may pick up
trace identifiers and other information from the task-local context from
where the log statement is being made.

The feature came with a [swift evolution style
proposal](https://github.com/apple/swift-log/blob/main/proposals/0001-metadata-providers.md)
introduction to the "why?" and "how?" of this feature you may find
interesting.

Metadata providers are used like this:

```swift
import Logging

enum Namespace { 
  @&#8203;TaskLocal static var simpleTraceID: String?
}

let simpleTraceIDMetadataProvider = Logger.MetadataProvider { 
    guard let traceID = Namespace.simpleTraceID else {
        return [:]
    }
    return ["simple-trace-id": .string(traceID)]
 }

LoggingSystem.bootstrap({ label, metadataProvider in
    myCoolLogHandler(label: label, metadataProvider: metadataProvider)
}, metadataProvider: simpleTraceIDMetadataProvider)
```

which in turn makes every `Logger` on this `LoggingSystem` add this
contextual metadata to log statements automatically:

```swift
let log = Logger(label: "hello")

Namespace.$simpleTraceID.withValue("1234-5678") {
  test()
}

func test() {
  log.info("test log statement")
}

// [info] [simple-trace-id: 1234-5678] test log statement
```

##### Adoption in `LogHandler`s

In order to support this new feature in your log handlers, please make
it accept a `MetadataProvider?` at creation, and store it as:

```swift
struct MyHandler: LogHandler {
    // ... 
    public var metadataProvider: Logger.MetadataProvider?
    // ...
}
```

#### What's Changed

##### Highlight

- Metadata Providers (e.g. for Distributed Tracing) in LogHandlers by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#238

##### Other changes

- \[docs] Minimal docc setup and landing page by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#226
- \=docc Make docs use symbol references by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#230
- \=docc Move to multiple Package.swift files by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#231
- Undo 5.7 package files, not needed yet by
[@&#8203;ktoso](https://github.com/ktoso) in
[apple/swift-log#232
- Update README: Add missing Source param by
[@&#8203;Rusik](https://github.com/Rusik) in
[apple/swift-log#233
- Fix build for wasm by [@&#8203;ahti](https://github.com/ahti) in
[apple/swift-log#236
- Add .spi.yml for Swift Package Index DocC support by
[@&#8203;yim-lee](https://github.com/yim-lee) in
[apple/swift-log#240
- Fixes link to Supabase repository in README.md by
[@&#8203;timobollwerk](https://github.com/timobollwerk) in
[apple/swift-log#245

#### New Contributors

- [@&#8203;Rusik](https://github.com/Rusik) made their first
contribution in
[apple/swift-log#233
- [@&#8203;ahti](https://github.com/ahti) made their first
contribution in
[apple/swift-log#236
- [@&#8203;timobollwerk](https://github.com/timobollwerk) made their
first contribution in
[apple/swift-log#245

**Full Changelog**:
apple/swift-log@1.4.4...1.5.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://app.renovatebot.com/dashboard#github/cgrindel/swift_bazel).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS4yMi4xIiwidXBkYXRlZEluVmVyIjoiMzUuMjIuMSJ9-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement Improvements to existing feature. semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants