Improve documentation & test coverage (#29)
This commit is contained in:
parent
613b53e0a3
commit
9be28a8450
|
@ -4,22 +4,23 @@ import PackageDescription
|
|||
let package = Package(
|
||||
name: "swift-baggage-context",
|
||||
products: [
|
||||
.library(name: "Baggage",
|
||||
.library(
|
||||
name: "Baggage",
|
||||
targets: [
|
||||
"Baggage"
|
||||
"Baggage",
|
||||
]
|
||||
),
|
||||
.library(name: "BaggageLogging",
|
||||
.library(
|
||||
name: "BaggageLogging",
|
||||
targets: [
|
||||
"BaggageLogging"
|
||||
"BaggageLogging",
|
||||
]
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0")
|
||||
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0"),
|
||||
],
|
||||
targets: [
|
||||
|
||||
.target(
|
||||
name: "Baggage",
|
||||
dependencies: []
|
||||
|
@ -39,7 +40,7 @@ let package = Package(
|
|||
.testTarget(
|
||||
name: "BaggageTests",
|
||||
dependencies: [
|
||||
"Baggage"
|
||||
"Baggage",
|
||||
]
|
||||
),
|
||||
|
||||
|
@ -47,7 +48,7 @@ let package = Package(
|
|||
name: "BaggageLoggingTests",
|
||||
dependencies: [
|
||||
"Baggage",
|
||||
"BaggageLogging"
|
||||
"BaggageLogging",
|
||||
]
|
||||
),
|
||||
|
||||
|
@ -60,7 +61,7 @@ let package = Package(
|
|||
"Baggage",
|
||||
"BaggageLogging",
|
||||
"BaggageBenchmarkTools",
|
||||
]
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "BaggageBenchmarkTools",
|
||||
|
|
148
README.md
148
README.md
|
@ -1,16 +1,24 @@
|
|||
# Baggage Context
|
||||
|
||||
[](https://swift.org/download/)
|
||||
[](https://swift.org/download/)
|
||||
[](https://swift.org/download/)
|
||||
[](https://github.com/slashmo/gsoc-swift-baggage-context/actions?query=workflow%3ACI)
|
||||
|
||||
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting tools such as tracers.
|
||||
It is purposefully not tied to any specific use-case (in the spirit of the [Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext), however it should enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our `BaggageContext` does not implement its own serialization scheme (today).
|
||||
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting
|
||||
tools such as tracers. It is purposefully not tied to any specific use-case (in the spirit of the
|
||||
[Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext). However, it should
|
||||
enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our
|
||||
`BaggageContext` does not implement its own serialization scheme (today).
|
||||
|
||||
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the [SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
|
||||
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to
|
||||
deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the
|
||||
[SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
|
||||
|
||||
## Installation
|
||||
|
||||
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`, so that's what you'd import in your Swift files.
|
||||
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`,
|
||||
so that's what you'd import in your Swift files.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
|
@ -22,14 +30,92 @@ dependencies: [
|
|||
]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`BaggageContext` is intended to be used in conjunction with the instrumentation of distributed systems. To make this
|
||||
instrumentation work, all parties involved operate on the same `BaggageContext` type. These are the three common
|
||||
parties, in no specific order, and guidance on how to use `BaggageContext`:
|
||||
|
||||
### End Users - explicit context passing
|
||||
|
||||
You'll likely interact with some API that takes a context. In most cases you already have a context at hand so you
|
||||
should pass that along. If you're certain you don't have a context at hand, pass along an empty one after thinking about
|
||||
why that's the case.
|
||||
|
||||
**TODO**: Document the reasoning behind `.background` & `.TODO` once merged ([#26](#26))
|
||||
|
||||
While this might seem like a burden to take on, this will allow you to immediately add instrumentation (e.g. tracing)
|
||||
once your application grows. Let's say your profiling some troublesome performance regressions. You won't have the time
|
||||
to go through the entire system to start passing contexts around.
|
||||
|
||||
> TL;DR: You should always pass around `BaggageContext`, so that you're ready for when you need it.
|
||||
|
||||
Once you are ready to instrument your application, you already have everything in place to get going. Instead of each
|
||||
instrument operating on its own context type they'll be using the same `BaggageContext` that you're already passing
|
||||
around to the various instrumentable libraries & frameworks you make use of, so you're free to mix & match any
|
||||
compatible instrument(s) 🙌 Check out the [swift-tracing](https://github.com/slashmo/gsoc-swift-tracing) repository for
|
||||
instructions on how to get up & running.
|
||||
|
||||
### Library & Framework Authors - passing context and instrumenting libraries
|
||||
|
||||
Developers creating frameworks/libraries (e.g. NIO, gRPC, AsyncHTTPClient, ...) which benefit from being instrumented
|
||||
should adopt `BaggageContext` as part of their public API. AsyncHTTPClient for example might accept a context like this:
|
||||
|
||||
```swift
|
||||
let context = BaggageContext()
|
||||
client.get(url: "https://swift.org", context: context)
|
||||
```
|
||||
|
||||
For more information on where to place this argument and how to name it, take a look at the
|
||||
[Context-Passing Guidelines](#Context-Passing-Guidelines).
|
||||
|
||||
Generally speaking, frameworks and libraries should treat baggage as an _opaque container_ and simply thread it along
|
||||
all asynchronous boundaries a call may have to go through. Libraries and frameworks should not attempt to reuse context
|
||||
as a means of passing values that they need for "normal" operation.
|
||||
|
||||
At cross-cutting boundaries, e.g. right before sending an HTTP
|
||||
request, they inject the `BaggageContext` into the HTTP headers, allowing context propagation. On the receiving side, an
|
||||
HTTP server should extract the request headers into a `BaggageContext`. Injecting/extracting is part of the
|
||||
`swift-tracing` libraries [and documented in its own repository](https://github.com/slashmo/gsoc-swift-tracing).
|
||||
|
||||
### Instrumentation Authors - defining, injecting and extracting baggage
|
||||
|
||||
When implementing instrumentation for cross-cutting tools, `BaggageContext` becomes the way you propagate metadata such
|
||||
as trace ids. Because each instrument knows what values might be added to the `BaggageContext` they are the ones
|
||||
creating `BaggageContextKey` types dictating the type of value associated with each key added to the context. To make
|
||||
accessing values a bit more convenient, we encourage you to add computed properties to `BaggageContextProtocol`:
|
||||
|
||||
```swift
|
||||
private enum TraceIDKey: BaggageContextKey {
|
||||
typealias Value = String
|
||||
}
|
||||
|
||||
extension BaggageContextProtocol {
|
||||
var traceID: String? {
|
||||
get {
|
||||
return self[TraceIDKey.self]
|
||||
}
|
||||
set {
|
||||
self[TraceIDKey.self] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var context = BaggageContext()
|
||||
context.traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
|
||||
print(context.traceID ?? "new trace id")
|
||||
```
|
||||
|
||||
## Context-Passing Guidelines
|
||||
|
||||
In order for context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
|
||||
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of guidelines:
|
||||
For context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
|
||||
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of
|
||||
guidelines:
|
||||
|
||||
### Argument naming/positioning
|
||||
|
||||
In order to propagate baggage through function calls (and asynchronous-boundaries it may often be necessary to pass it explicitly (unless wrapper APIs are provided which handle the propagation automatically).
|
||||
Propagating baggage context through your system is to be done explicitly, meaning as a parameter in function calls,
|
||||
following the "flow" of execution.
|
||||
|
||||
When passing baggage context explicitly we strongly suggest sticking to the following style guideline:
|
||||
|
||||
|
@ -38,16 +124,20 @@ When passing baggage context explicitly we strongly suggest sticking to the foll
|
|||
2. Defaulted non-function parameters (e.g. `(mode: Mode = .default)`),
|
||||
3. Required function parameters, including required trailing closures (e.g. `(onNext elementHandler: (Value) -> ())`),
|
||||
4. Defaulted function parameters, including optional trailing closures (e.g. `(onComplete completionHandler: (Reason) -> ()) = { _ in }`).
|
||||
- Baggage Context should be passed as: **the last parameter in the required non-function parameters group in a function declaration**.
|
||||
- Baggage Context should be passed as **the last parameter in the required non-function parameters group in a function declaration**.
|
||||
|
||||
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and the method signature remains human-readable and “Swifty”.
|
||||
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and
|
||||
the method signature remains human-readable and “Swifty”.
|
||||
|
||||
Examples:
|
||||
|
||||
- `func request(_ url: URL,` **`context: BaggageContext`** `)`, which may be called as `httpClient.request(url, context: context)`
|
||||
- `func handle(_ request: RequestObject,` **`context: BaggageContextCarrier`** `)`
|
||||
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context together with the baggage;
|
||||
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases, in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage` spelling when the baggage is contained in a framework context object.
|
||||
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context
|
||||
together with the baggage;
|
||||
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases,
|
||||
in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage`
|
||||
spelling when the baggage is contained in a framework context object.
|
||||
- `func receiveMessage(_ message: Message, context: FrameworkContext)`
|
||||
- `func handle(element: Element,` **`context: BaggageContextCarrier`** `, settings: Settings? = nil)`
|
||||
- before any defaulted non-function parameters
|
||||
|
@ -58,29 +148,45 @@ Examples:
|
|||
In case there are _multiple_ "framework-ish" parameters, such as passing a NIO `EventLoop` or similar, we suggest:
|
||||
|
||||
- `func perform(_ work: Work, for user: User,` _`frameworkThing: Thing, eventLoop: NIO.EventLoop,`_ **`context: BaggageContext`** `)`
|
||||
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any specific framework parameter - as it is expected that any framework should be accepting a context if it is able to do so. While not all libraries are necessarily going to be implemented using the same frameworks.
|
||||
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any
|
||||
specific framework parameter - as it is expected that any framework should be accepting a context if it can do so.
|
||||
While not all libraries are necessarily going to be implemented using the same frameworks.
|
||||
|
||||
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to keep the read-out-loud phrasing of methods to remain _"request that url (ignore reading out loud the context parameter)"_ rather than _"request (ignore this context parameter when reading) that url"_.
|
||||
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to
|
||||
keep the read-out-loud phrasing of methods to remain _"request that URL (ignore reading out loud the context parameter)"_
|
||||
rather than _"request (ignore this context parameter when reading) that URL"_.
|
||||
|
||||
#### When to use what context type?
|
||||
|
||||
This library defines the following context (carrier) types:
|
||||
|
||||
- `struct BaggageContext` - which is the actual context object,
|
||||
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily care where it gets a `context` value from
|
||||
- this pattern enables other frameworks to pass their `FrameworkContext`, like so: `get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a good example, or Lambda Runtime's `Lambda.Context`
|
||||
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily
|
||||
care where it gets a `context` value from
|
||||
- this pattern enables other frameworks to pass their `FrameworkContext`, like so:
|
||||
`get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a
|
||||
good example, or Lambda Runtime's `Lambda.Context`
|
||||
- `protocol LoggingBaggageContextCarrier` - which in addition exposes a logger bound to the passed context
|
||||
|
||||
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API work with any context, or is it always going to require _my framework context_, and erring on accepting the most general type possible.
|
||||
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically
|
||||
a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API
|
||||
work with any context, or is it always going to require _my framework context_, and erring on accepting the most
|
||||
general type possible.
|
||||
|
||||
#### Existing context argument
|
||||
|
||||
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which is expected to be passed through "everywhere", we suggest to follow these guidelines to adopting BaggageContext:
|
||||
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which
|
||||
is expected to be passed through "everywhere", we suggest to follow these guidelines for adopting BaggageContext:
|
||||
|
||||
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your users becomes `context.baggage` (rather than the confusing `context.context`)
|
||||
2. If you cannot or it would not make sense to carry baggage inside your framework's context object, pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
|
||||
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other cases
|
||||
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to disambiguate your `context` object from the `baggage` context object.
|
||||
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your
|
||||
users becomes `context.baggage` (rather than the confusing `context.context`)
|
||||
2. If you cannot or it would not make sense to carry baggage inside your framework's context object,
|
||||
pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
|
||||
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other
|
||||
cases
|
||||
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework
|
||||
context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to
|
||||
disambiguate your `context` object from the `baggage` context object.
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
/// typealias Value = String
|
||||
/// }
|
||||
///
|
||||
/// var baggage = BaggageContext()
|
||||
/// var context = BaggageContext()
|
||||
/// // set a new value
|
||||
/// baggage[TestIDKey.self] = "abc"
|
||||
/// context[TestIDKey.self] = "abc"
|
||||
/// // retrieve a stored value
|
||||
/// baggage[TestIDKey.self] ?? "default"
|
||||
/// context[TestIDKey.self] ?? "default"
|
||||
/// // remove a stored value
|
||||
/// baggage[TestIDKey.self] = nil
|
||||
/// context[TestIDKey.self] = nil
|
||||
///
|
||||
/// ## Convenience extensions
|
||||
///
|
||||
|
@ -36,7 +36,7 @@
|
|||
/// using the following pattern:
|
||||
///
|
||||
/// extension BaggageContextProtocol {
|
||||
/// var testID: TestIDKey.Value {
|
||||
/// var testID: TestIDKey.Value? {
|
||||
/// get {
|
||||
/// self[TestIDKey.self]
|
||||
/// } set {
|
||||
|
@ -45,37 +45,34 @@
|
|||
/// }
|
||||
/// }
|
||||
public struct BaggageContext: BaggageContextProtocol {
|
||||
private var _storage = [AnyBaggageContextKey: ValueContainer]()
|
||||
private var _storage = [AnyBaggageContextKey: Any]()
|
||||
|
||||
/// Create an empty `BaggageContext`.
|
||||
public init() {}
|
||||
|
||||
public subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? {
|
||||
get {
|
||||
return self._storage[AnyBaggageContextKey(key)]?.forceUnwrap(key)
|
||||
guard let value = self._storage[AnyBaggageContextKey(key)] else { return nil }
|
||||
// safe to force-cast as this subscript is the only way to set a value.
|
||||
return (value as! Key.Value)
|
||||
} set {
|
||||
self._storage[AnyBaggageContextKey(key)] = newValue.map {
|
||||
ValueContainer(value: $0)
|
||||
}
|
||||
self._storage[AnyBaggageContextKey(key)] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
|
||||
self._storage.forEach { key, container in
|
||||
callback(key, container.value)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ValueContainer {
|
||||
let value: Any
|
||||
|
||||
func forceUnwrap<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value {
|
||||
return self.value as! Key.Value
|
||||
public func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows {
|
||||
try self._storage.forEach { key, value in
|
||||
try body(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BaggageContext: CustomStringConvertible {
|
||||
/// A context's description prints only keys of the contained values.
|
||||
/// This is in order to prevent spilling a lot of detailed information of carried values accidentally.
|
||||
///
|
||||
/// `BaggageContext`s are not intended to be printed "raw" but rather inter-operate with tracing, logging and other systems,
|
||||
/// which can use the `forEach` function providing access to its underlying values.
|
||||
public var description: String {
|
||||
return "\(type(of: self).self)(keys: \(self._storage.map { $0.key.name }))"
|
||||
}
|
||||
|
@ -88,7 +85,7 @@ public protocol BaggageContextProtocol {
|
|||
/// using the following pattern:
|
||||
///
|
||||
/// extension BaggageContextProtocol {
|
||||
/// var testID: TestIDKey.Value {
|
||||
/// var testID: TestIDKey.Value? {
|
||||
/// get {
|
||||
/// self[TestIDKey.self]
|
||||
/// } set {
|
||||
|
@ -98,17 +95,30 @@ public protocol BaggageContextProtocol {
|
|||
/// }
|
||||
subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? { get set }
|
||||
|
||||
/// Iterates over the baggage context's contents invoking the callback one-by one.
|
||||
/// Calls the given closure on each key/value pair in the `BaggageContext`.
|
||||
///
|
||||
/// - Parameter callback: invoked with the type erased key and value stored for the key in this baggage.
|
||||
func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void)
|
||||
/// - Parameter body: A closure invoked with the type erased key and value stored for the key in this baggage.
|
||||
func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows
|
||||
}
|
||||
|
||||
// ==== ------------------------------------------------------------------------
|
||||
// MARK: Baggage keys
|
||||
|
||||
/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` gurantees type-safety.
|
||||
/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` guarantees type-safety.
|
||||
/// To give your `BaggageContextKey` an explicit name you may override the `name` property.
|
||||
///
|
||||
/// In general, `BaggageContextKey`s should be `internal` to the part of a system using it. It is strongly recommended to do
|
||||
/// convenience extensions on `BaggageContextProtocol`, using the keys directly is considered an anti-pattern.
|
||||
///
|
||||
/// extension BaggageContextProtocol {
|
||||
/// var testID: TestIDKey.Value? {
|
||||
/// get {
|
||||
/// self[TestIDKey.self]
|
||||
/// } set {
|
||||
/// self[TestIDKey.self] = newValue
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
public protocol BaggageContextKey {
|
||||
/// The type of `Value` uniquely identified by this key.
|
||||
associatedtype Value
|
||||
|
@ -121,7 +131,9 @@ extension BaggageContextKey {
|
|||
public static var name: String? { return nil }
|
||||
}
|
||||
|
||||
/// A type-erased `BaggageContextKey` used when iterating through the `BaggageContext` using its `forEach` method.
|
||||
public struct AnyBaggageContextKey {
|
||||
/// The key's type represented erased to an `Any.Type`.
|
||||
public let keyType: Any.Type
|
||||
|
||||
private let _name: String?
|
||||
|
@ -132,7 +144,7 @@ public struct AnyBaggageContextKey {
|
|||
return self._name ?? String(describing: self.keyType.self)
|
||||
}
|
||||
|
||||
public init<Key>(_ keyType: Key.Type) where Key: BaggageContextKey {
|
||||
init<Key>(_ keyType: Key.Type) where Key: BaggageContextKey {
|
||||
self.keyType = keyType
|
||||
self._name = keyType.name
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ extension BaggageContextCarrier {
|
|||
}
|
||||
}
|
||||
|
||||
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
|
||||
self.baggage.forEach(callback)
|
||||
public func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows {
|
||||
try self.baggage.forEach(body)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ class ArgumentParser<U> {
|
|||
) {
|
||||
self.arguments.append(
|
||||
Argument(name: name, help: help)
|
||||
{ try self.parseArgument(name, property, defaultValue, parser) }
|
||||
{ try self.parseArgument(name, property, defaultValue, parser) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,12 @@ import Logging
|
|||
// MARK: BaggageContext (as additional Logger.Metadata) LogHandler
|
||||
|
||||
/// Proxying log handler which adds `BaggageContext` as metadata when log events are to be emitted.
|
||||
///
|
||||
/// The values stored in the `BaggageContext` are merged with the existing metadata on the logger. If both contain values for the same key,
|
||||
/// the `BaggageContext` values are preferred.
|
||||
public struct BaggageMetadataLogHandler: LogHandler {
|
||||
var underlying: Logger
|
||||
let context: BaggageContext
|
||||
private var underlying: Logger
|
||||
private let context: BaggageContext
|
||||
|
||||
public init(logger underlying: Logger, context: BaggageContext) {
|
||||
self.underlying = underlying
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
import Baggage
|
||||
import Logging
|
||||
|
||||
/// A `BaggageContextLogging` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
|
||||
/// A `LoggingBaggageContextCarrier` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
|
||||
/// and to such frameworks to pass their context as `BaggageContextCarrier`.
|
||||
public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
|
||||
/// The logger associated with this carrier context.
|
||||
/// The logger associated with this context carrier.
|
||||
///
|
||||
/// It should automatically populate the loggers metadata based on the `BaggageContext` associated with this context object.
|
||||
///
|
||||
|
@ -27,7 +27,18 @@ public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
|
|||
/// SHOULD implement this logger by wrapping the "raw" logger associated with this context with the `logger.with(BaggageContext:)` function,
|
||||
/// which efficiently handles the bridging of baggage to logging metadata values.
|
||||
///
|
||||
/// ### Example implementation
|
||||
///
|
||||
/// Writes to the `logger` metadata SHOULD NOT be reflected in the `baggage`,
|
||||
/// however writes to the underlying `baggage` SHOULD be reflected in the `logger`.
|
||||
///
|
||||
/// struct MyFrameworkContext: LoggingBaggageContextCarrier {
|
||||
/// var baggage = BaggageContext()
|
||||
/// private let _logger: Logger
|
||||
///
|
||||
/// var logger: Logger {
|
||||
/// return self._logger.with(context: self.baggage)
|
||||
/// }
|
||||
/// }
|
||||
var logger: Logger { get }
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ extension LoggingBaggageContextCarrierTests {
|
|||
return [
|
||||
("test_ContextWithLogger_dumpBaggage", test_ContextWithLogger_dumpBaggage),
|
||||
("test_ContextWithLogger_log_withBaggage", test_ContextWithLogger_log_withBaggage),
|
||||
("test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata", test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,17 +64,34 @@ final class LoggingBaggageContextCarrierTests: XCTestCase {
|
|||
"secondIDExplicitlyNamed": "value",
|
||||
])
|
||||
}
|
||||
|
||||
func test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata() {
|
||||
let baggage = BaggageContext()
|
||||
let logging = TestLogging()
|
||||
var logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
|
||||
logger[metadataKey: "secondIDExplicitlyNamed"] = "set on logger"
|
||||
|
||||
var context: LoggingBaggageContextCarrier = ExampleFrameworkContext(context: baggage, logger: logger)
|
||||
|
||||
context.secondTestID = "set on baggage"
|
||||
|
||||
context.logger.info("Hello")
|
||||
|
||||
logging.history.assertExist(level: .info, message: "Hello", metadata: [
|
||||
"secondIDExplicitlyNamed": "set on baggage",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public struct ExampleFrameworkContext: LoggingBaggageContextCarrier {
|
||||
public var baggage: BaggageContext
|
||||
struct ExampleFrameworkContext: LoggingBaggageContextCarrier {
|
||||
var baggage: BaggageContext
|
||||
|
||||
private var _logger: Logger
|
||||
public var logger: Logger {
|
||||
var logger: Logger {
|
||||
return self._logger.with(context: self.baggage)
|
||||
}
|
||||
|
||||
public init(context baggage: BaggageContext, logger: Logger) {
|
||||
init(context baggage: BaggageContext, logger: Logger) {
|
||||
self.baggage = baggage
|
||||
self._logger = logger
|
||||
}
|
||||
|
|
|
@ -194,7 +194,8 @@ extension History {
|
|||
metadata: Logger.Metadata? = nil,
|
||||
source: String? = nil,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
line: UInt = #line)
|
||||
{
|
||||
let source = source ?? Logger.currentModule(filePath: "\(file)")
|
||||
let entry = self.find(level: level, message: message, metadata: metadata, source: source)
|
||||
XCTAssertNotNil(
|
||||
|
@ -214,7 +215,8 @@ extension History {
|
|||
metadata: Logger.Metadata? = nil,
|
||||
source: String? = nil,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
line: UInt = #line)
|
||||
{
|
||||
let source = source ?? Logger.currentModule(filePath: "\(file)")
|
||||
let entry = self.find(level: level, message: message, metadata: metadata, source: source)
|
||||
XCTAssertNil(
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Baggage Context open source project
|
||||
//
|
||||
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// BaggageContextCarrierTests+XCTest.swift
|
||||
//
|
||||
import XCTest
|
||||
///
|
||||
/// NOTE: This file was generated by generate_linux_tests.rb
|
||||
///
|
||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||
///
|
||||
|
||||
extension BaggageContextCarrierTests {
|
||||
|
||||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
||||
static var allTests : [(String, (BaggageContextCarrierTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("testBaggageContextSubscript", testBaggageContextSubscript),
|
||||
("testBaggageContextForEach", testBaggageContextForEach),
|
||||
("testBaggageContextCarriesItself", testBaggageContextCarriesItself),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Baggage Context open source project
|
||||
//
|
||||
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import Baggage
|
||||
import XCTest
|
||||
|
||||
final class BaggageContextCarrierTests: XCTestCase {
|
||||
func testBaggageContextSubscript() {
|
||||
var carrier = TestFrameworkContext()
|
||||
|
||||
// mutate baggage context through carrier
|
||||
carrier[TestKey.self] = 42
|
||||
XCTAssertEqual(carrier[TestKey.self], 42)
|
||||
XCTAssertEqual(carrier.baggage[TestKey.self], 42)
|
||||
|
||||
// mutate baggage context directly
|
||||
carrier.baggage[OtherKey.self] = "test"
|
||||
XCTAssertEqual(carrier.baggage[OtherKey.self], "test")
|
||||
XCTAssertEqual(carrier[OtherKey.self], "test")
|
||||
}
|
||||
|
||||
func testBaggageContextForEach() {
|
||||
var contents = [AnyBaggageContextKey: Any]()
|
||||
var carrier = TestFrameworkContext()
|
||||
|
||||
carrier[TestKey.self] = 42
|
||||
carrier[OtherKey.self] = "test"
|
||||
|
||||
carrier.forEach { key, value in
|
||||
contents[key] = value
|
||||
}
|
||||
|
||||
XCTAssertNotNil(contents[AnyBaggageContextKey(TestKey.self)])
|
||||
XCTAssertEqual(contents[AnyBaggageContextKey(TestKey.self)] as? Int, 42)
|
||||
XCTAssertNotNil(contents[AnyBaggageContextKey(OtherKey.self)])
|
||||
XCTAssertEqual(contents[AnyBaggageContextKey(OtherKey.self)] as? String, "test")
|
||||
}
|
||||
|
||||
func testBaggageContextCarriesItself() {
|
||||
var context: BaggageContextCarrier = BaggageContext()
|
||||
|
||||
context.baggage[TestKey.self] = 42
|
||||
XCTAssertEqual(context.baggage[TestKey.self], 42)
|
||||
}
|
||||
}
|
||||
|
||||
private struct TestFrameworkContext: BaggageContextCarrier {
|
||||
var baggage = BaggageContext()
|
||||
}
|
||||
|
||||
private enum TestKey: BaggageContextKey {
|
||||
typealias Value = Int
|
||||
}
|
||||
|
||||
private enum OtherKey: BaggageContextKey {
|
||||
typealias Value = String
|
||||
}
|
|
@ -62,7 +62,6 @@ final class BaggageContextTests: XCTestCase {
|
|||
// use contains instead of `XCTAssertEqual` because the order is non-predictable (Dictionary)
|
||||
XCTAssert(description.contains("TestIDKey"))
|
||||
XCTAssert(description.contains("ExplicitKeyName"))
|
||||
print(description.reversed().starts(with: "])"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ class LinuxMainRunnerImpl: LinuxMainRunner {
|
|||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
||||
func run() {
|
||||
XCTMain([
|
||||
testCase(BaggageContextCarrierTests.allTests),
|
||||
testCase(BaggageContextTests.allTests),
|
||||
testCase(LoggingBaggageContextCarrierTests.allTests),
|
||||
])
|
||||
|
|
Loading…
Reference in New Issue