-
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
Metadata Providers (e.g. for Distributed Tracing) in LogHandlers #238
Conversation
d4805c3
to
80133fc
Compare
8e1ba53
to
3d53099
Compare
Tests/LoggingTests/LoggingTest.swift
Outdated
@@ -873,18 +920,20 @@ class LoggingTest: XCTestCase { | |||
} | |||
} | |||
|
|||
extension Logger { | |||
public extension Logger { |
There was a problem hiding this comment.
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
4c543ab
to
45f506c
Compare
d9582e2
to
92fc671
Compare
scripts/generate_linux_tests.rb
Outdated
end | ||
|
||
false | ||
end |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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)
There was a problem hiding this 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 {} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 🤔
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😅
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Tests/LoggingTests/TestLogger.swift
Outdated
) | ||
} | ||
|
||
func makeWithMetadataProvider(label: String, metadataProvider: Logger.MetadataProvider) -> LogHandler { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Sources/Logging/Logging.swift
Outdated
@@ -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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Sources/Logging/Logging.swift
Outdated
self._factory.replaceFactory(factory, validate: false) | ||
} | ||
|
||
fileprivate static var factory: (String) -> LogHandler { | ||
return self._factory.underlying | ||
internal static func bootstrapInternal(metadataProvider: Logger.MetadataProvider) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?) {
Sources/Logging/Logging.swift
Outdated
/// - 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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😄
Sources/Logging/Logging.swift
Outdated
}) | ||
} | ||
|
||
init(label: String, metadataProvider: @escaping MetadataProvider.Function) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Co-authored-by: Moritz Lang <[email protected]>
Co-authored-by: Yim Lee <[email protected]>
f7dd9cd
to
91437c0
Compare
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? |
d798dcb
to
33e0316
Compare
/// | ||
/// - parameters: | ||
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`. | ||
public static func bootstrap(_ factory: @escaping (String) -> LogHandler) { |
There was a problem hiding this comment.
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
…ystem.metadataProvider
33e0316
to
3d8e543
Compare
514e8e0
to
561c5aa
Compare
@@ -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) |
There was a problem hiding this comment.
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"™
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 🥳 |
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. |
[![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 [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/252](https://github.com/apple/swift-log/pull/252) - Handle providers properly in multiplex log handler by [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/254](https://github.com/apple/swift-log/pull/254) - Add CI for Swift 5.8 and update nightly to Ubuntu 22.04 by [@​yim-lee](https://github.com/yim-lee) in [https://github.com/apple/swift-log/pull/255](https://github.com/apple/swift-log/pull/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 [@​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 [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/250](https://github.com/apple/swift-log/pull/250) - Emit correct metadata from StreamLogHandler by [@​slashmo](https://github.com/slashmo) in [https://github.com/apple/swift-log/pull/251](https://github.com/apple/swift-log/pull/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 { @​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 [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/238](https://github.com/apple/swift-log/pull/238) ##### Other changes - \[docs] Minimal docc setup and landing page by [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/226](https://github.com/apple/swift-log/pull/226) - \=docc Make docs use symbol references by [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/230](https://github.com/apple/swift-log/pull/230) - \=docc Move to multiple Package.swift files by [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/231](https://github.com/apple/swift-log/pull/231) - Undo 5.7 package files, not needed yet by [@​ktoso](https://github.com/ktoso) in [https://github.com/apple/swift-log/pull/232](https://github.com/apple/swift-log/pull/232) - Update README: Add missing Source param by [@​Rusik](https://github.com/Rusik) in [https://github.com/apple/swift-log/pull/233](https://github.com/apple/swift-log/pull/233) - Fix build for wasm by [@​ahti](https://github.com/ahti) in [https://github.com/apple/swift-log/pull/236](https://github.com/apple/swift-log/pull/236) - Add .spi.yml for Swift Package Index DocC support by [@​yim-lee](https://github.com/yim-lee) in [https://github.com/apple/swift-log/pull/240](https://github.com/apple/swift-log/pull/240) - Fixes link to Supabase repository in README.md by [@​timobollwerk](https://github.com/timobollwerk) in [https://github.com/apple/swift-log/pull/245](https://github.com/apple/swift-log/pull/245) #### New Contributors - [@​Rusik](https://github.com/Rusik) made their first contribution in [https://github.com/apple/swift-log/pull/233](https://github.com/apple/swift-log/pull/233) - [@​ahti](https://github.com/ahti) made their first contribution in [https://github.com/apple/swift-log/pull/236](https://github.com/apple/swift-log/pull/236) - [@​timobollwerk](https://github.com/timobollwerk) made their first contribution in [https://github.com/apple/swift-log/pull/245](https://github.com/apple/swift-log/pull/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>
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:
Baggage
metadataProvider: .otel
can and will pick up baggage from task-locals, since they do depend on baggage and tracing APIs.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: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:
which is accounted for by a default implementation:
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.