Final API proposal - Baggage + Context (#34)
This commit is contained in:
parent
11ce8e1b0e
commit
dbcbed943e
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "swift-log",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "173f567a2dfec11d74588eea82cecea555bdc0bc",
|
||||||
|
"version": "1.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "swift-baggage-context",
|
name: "swift-context",
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "Baggage",
|
name: "Baggage",
|
||||||
|
@ -11,14 +11,14 @@ let package = Package(
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.library(
|
.library(
|
||||||
name: "BaggageLogging",
|
name: "BaggageContext",
|
||||||
targets: [
|
targets: [
|
||||||
"BaggageLogging",
|
"BaggageContext",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
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.4.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
@ -27,7 +27,7 @@ let package = Package(
|
||||||
),
|
),
|
||||||
|
|
||||||
.target(
|
.target(
|
||||||
name: "BaggageLogging",
|
name: "BaggageContext",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Baggage",
|
"Baggage",
|
||||||
.product(name: "Logging", package: "swift-log"),
|
.product(name: "Logging", package: "swift-log"),
|
||||||
|
@ -45,10 +45,10 @@ let package = Package(
|
||||||
),
|
),
|
||||||
|
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "BaggageLoggingTests",
|
name: "BaggageContextTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Baggage",
|
"Baggage",
|
||||||
"BaggageLogging",
|
"BaggageContext",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -56,15 +56,14 @@ let package = Package(
|
||||||
// MARK: Performance / Benchmarks
|
// MARK: Performance / Benchmarks
|
||||||
|
|
||||||
.target(
|
.target(
|
||||||
name: "BaggageBenchmarks",
|
name: "BaggageContextBenchmarks",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Baggage",
|
"BaggageContext",
|
||||||
"BaggageLogging",
|
"BaggageContextBenchmarkTools",
|
||||||
"BaggageBenchmarkTools",
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "BaggageBenchmarkTools",
|
name: "BaggageContextBenchmarkTools",
|
||||||
dependencies: []
|
dependencies: []
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Baggage
|
||||||
|
|
||||||
|
/// A `Baggage` is a heterogeneous storage type with value semantics for keyed values in a type-safe fashion.
|
||||||
|
///
|
||||||
|
/// Its values are uniquely identified via `Baggage.Key`s (by type identity). These keys also dictate the type of
|
||||||
|
/// value allowed for a specific key-value pair through their associated type `Value`.
|
||||||
|
///
|
||||||
|
/// ## Defining keys and accessing values
|
||||||
|
/// Baggage keys are defined as types, most commonly case-less enums (as no actual instances are actually required)
|
||||||
|
/// which conform to the `Baggage.Key` protocol:
|
||||||
|
///
|
||||||
|
/// private enum TestIDKey: Baggage.Key {
|
||||||
|
/// typealias Value = String
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// While defining a key, one should also immediately declare an extension on `Baggage`,
|
||||||
|
/// to allow convenient and discoverable ways to interact with the baggage item, the extension should take the form of:
|
||||||
|
///
|
||||||
|
/// extension Baggage {
|
||||||
|
/// var testID: String? {
|
||||||
|
/// get {
|
||||||
|
/// self[TestIDKey.self]
|
||||||
|
/// } set {
|
||||||
|
/// self[TestIDKey.self] = newValue
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// For consistency, it is recommended to name key types with the `...Key` suffix (e.g. `SomethingKey`) and the property
|
||||||
|
/// used to access a value identifier by such key the prefix of the key (e.g. `something`). Please also observe the usual
|
||||||
|
/// Swift naming conventions, e.g. prefer `ID` to `Id` etc.
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
/// Using a baggage container is fairly straight forward, as it boils down to using the prepared computed properties:
|
||||||
|
///
|
||||||
|
/// var baggage = Baggage.topLevel
|
||||||
|
/// // set a new value
|
||||||
|
/// baggage.testID = "abc"
|
||||||
|
/// // retrieve a stored value
|
||||||
|
/// let testID = baggage.testID ?? "default"
|
||||||
|
/// // remove a stored value
|
||||||
|
/// baggage.testIDKey = nil
|
||||||
|
///
|
||||||
|
/// Note that normally a baggage should not be "created" ad-hoc by user code, but rather it should be passed to it from
|
||||||
|
/// a runtime. For example, when working in an HTTP server framework, it is most likely that the baggage is already passed
|
||||||
|
/// directly or indirectly (e.g. in a `FrameworkContext`)
|
||||||
|
///
|
||||||
|
/// ### Accessing all values
|
||||||
|
///
|
||||||
|
/// The only way to access "all" values in a baggage context is by using the `forEach` function.
|
||||||
|
/// The baggage container on purpose does not expose more functions to prevent abuse and treating it as too much of an
|
||||||
|
/// arbitrary value smuggling container, but only make it convenient for tracing and instrumentation systems which need
|
||||||
|
/// to access either specific or all items carried inside a baggage.
|
||||||
|
public struct Baggage {
|
||||||
|
public typealias Key = BaggageKey
|
||||||
|
|
||||||
|
private var _storage = [AnyBaggageKey: Any]()
|
||||||
|
|
||||||
|
/// Internal on purpose, please use `Baggage.TODO` or `Baggage.topLevel` to create an "empty" context,
|
||||||
|
/// which carries more meaning to other developers why an empty context was used.
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
/// Creates a new empty "top level" baggage, generally used as an "initial" baggage to immediately be populated with
|
||||||
|
/// some values by a framework or runtime. Another use case is for tasks starting in the "background" (e.g. on a timer),
|
||||||
|
/// which don't have a "request context" per se that they can pick up, and as such they have to create a "top level"
|
||||||
|
/// baggage for their work.
|
||||||
|
///
|
||||||
|
/// ## Usage in frameworks and libraries
|
||||||
|
/// This function is really only intended to be used frameworks and libraries, at the "top-level" where a request's,
|
||||||
|
/// message's or task's processing is initiated. For example, a framework handling requests, should create an empty
|
||||||
|
/// context when handling a request only to immediately populate it with useful trace information extracted from e.g.
|
||||||
|
/// request headers.
|
||||||
|
///
|
||||||
|
/// ## Usage in applications
|
||||||
|
/// Application code should never have to create an empty context during the processing lifetime of any request,
|
||||||
|
/// and only should create contexts if some processing is performed in the background - thus the naming of this property.
|
||||||
|
///
|
||||||
|
/// Usually, a framework such as an HTTP server or similar "request handler" would already provide users
|
||||||
|
/// with a context to be passed along through subsequent calls.
|
||||||
|
///
|
||||||
|
/// If unsure where to obtain a context from, prefer using `.TODO("Not sure where I should get a context from here?")`,
|
||||||
|
/// in order to inform other developers that the lack of context passing was not done on purpose, but rather because either
|
||||||
|
/// not being sure where to obtain a context from, or other framework limitations -- e.g. the outer framework not being
|
||||||
|
/// baggage context aware just yet.
|
||||||
|
public static var topLevel: Baggage {
|
||||||
|
return Baggage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
/// A baggage intended as a placeholder until a real value can be passed through a function call.
|
||||||
|
///
|
||||||
|
/// It should ONLY be used while prototyping or when the passing of the proper context is not yet possible,
|
||||||
|
/// e.g. because an external library did not pass it correctly and has to be fixed before the proper context
|
||||||
|
/// can be obtained where the TO-DO is currently used.
|
||||||
|
///
|
||||||
|
/// ## Crashing on TO-DO context creation
|
||||||
|
/// You may set the `BAGGAGE_CRASH_TODOS` variable while compiling a project in order to make calls to this function crash
|
||||||
|
/// with a fatal error, indicating where a to-do baggage context was used. This comes in handy when wanting to ensure that
|
||||||
|
/// a project never ends up using with code initially was written as "was lazy, did not pass context", yet the
|
||||||
|
/// project requires context passing to be done correctly throughout the application. Similar checks can be performed
|
||||||
|
/// at compile time easily using linters (not yet implemented), since it is always valid enough to detect a to-do context
|
||||||
|
/// being passed as illegal and warn or error when spotted.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// let baggage = Baggage.TODO("The framework XYZ should be modified to pass us a context here, and we'd pass it along"))
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - reason: Informational reason for developers, why a placeholder context was used instead of a proper one,
|
||||||
|
/// - Returns: Empty "to-do" baggage context which should be eventually replaced with a carried through one, or `background`.
|
||||||
|
public static func TODO(_ reason: StaticString? = "", function: String = #function, file: String = #file, line: UInt = #line) -> Baggage {
|
||||||
|
var context = Baggage.topLevel
|
||||||
|
#if BAGGAGE_CRASH_TODOS
|
||||||
|
fatalError("BAGGAGE_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)")
|
||||||
|
#else
|
||||||
|
context[TODOKey.self] = .init(file: file, line: line)
|
||||||
|
return context
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TODOKey: BaggageKey {
|
||||||
|
typealias Value = TODOLocation
|
||||||
|
static var nameOverride: String? {
|
||||||
|
return "todo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
/// Provides type-safe access to the baggage's values.
|
||||||
|
/// This API should ONLY be used inside of accessor implementations.
|
||||||
|
///
|
||||||
|
/// End users rather than using this subscript should use "accessors" the key's author MUST define, following this pattern:
|
||||||
|
///
|
||||||
|
/// internal enum TestID: Baggage.Key {
|
||||||
|
/// typealias Value = TestID
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// extension Baggage {
|
||||||
|
/// public internal(set) var testID: TestID? {
|
||||||
|
/// get {
|
||||||
|
/// self[TestIDKey.self]
|
||||||
|
/// }
|
||||||
|
/// set {
|
||||||
|
/// self[TestIDKey.self] = newValue
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// This is in order to enforce a consistent style across projects and also allow for fine grained control over
|
||||||
|
/// who may set and who may get such property. Just access control to the Key type itself lacks such fidelity.
|
||||||
|
///
|
||||||
|
/// Note that specific baggage and context types MAY (and usually do), offer also a way to set baggage values,
|
||||||
|
/// however in the most general case it is not required, as some frameworks may only be able to offer reading.
|
||||||
|
public subscript<Key: BaggageKey>(_ key: Key.Type) -> Key.Value? {
|
||||||
|
get {
|
||||||
|
guard let value = self._storage[AnyBaggageKey(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[AnyBaggageKey(key)] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of contained baggage items.
|
||||||
|
public var count: Int {
|
||||||
|
return self._storage.count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the given closure for each item contained in the underlying `Baggage`.
|
||||||
|
///
|
||||||
|
/// Order of those invocations is NOT guaranteed and should not be relied on.
|
||||||
|
///
|
||||||
|
/// - Parameter body: A closure invoked with the type erased key and value stored for the key in this baggage.
|
||||||
|
public func forEach(_ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
|
||||||
|
try self._storage.forEach { key, value in
|
||||||
|
try body(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage: 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.
|
||||||
|
///
|
||||||
|
/// `Baggage`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 }))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Carried automatically by a "to do" baggage.
|
||||||
|
/// It can be used to track where a context originated and which "to do" context must be fixed into a real one to avoid this.
|
||||||
|
public struct TODOLocation {
|
||||||
|
/// Source file location where the to-do `Baggage` was created
|
||||||
|
public let file: String
|
||||||
|
/// Source line location where the to-do `Baggage` was created
|
||||||
|
public let line: UInt
|
||||||
|
}
|
|
@ -1,239 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
/// A `BaggageContext` is a heterogeneous storage type with value semantics for keyed values in a type-safe
|
|
||||||
/// fashion. Its values are uniquely identified via `BaggageContextKey`s. These keys also dictate the type of
|
|
||||||
/// value allowed for a specific key-value pair through their associated type `Value`.
|
|
||||||
///
|
|
||||||
/// ## Subscript access
|
|
||||||
/// You may access the stored values by subscripting with a key type conforming to `BaggageContextKey`.
|
|
||||||
///
|
|
||||||
/// enum TestIDKey: BaggageContextKey {
|
|
||||||
/// typealias Value = String
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// var context = BaggageContext.background
|
|
||||||
/// // set a new value
|
|
||||||
/// context[TestIDKey.self] = "abc"
|
|
||||||
/// // retrieve a stored value
|
|
||||||
/// context[TestIDKey.self] ?? "default"
|
|
||||||
/// // remove a stored value
|
|
||||||
/// context[TestIDKey.self] = nil
|
|
||||||
///
|
|
||||||
/// ## Convenience extensions
|
|
||||||
///
|
|
||||||
/// Libraries may also want to provide an extension, offering the values that users are expected to reach for
|
|
||||||
/// using the following pattern:
|
|
||||||
///
|
|
||||||
/// extension BaggageContextProtocol {
|
|
||||||
/// var testID: TestIDKey.Value? {
|
|
||||||
/// get {
|
|
||||||
/// self[TestIDKey.self]
|
|
||||||
/// } set {
|
|
||||||
/// self[TestIDKey.self] = newValue
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
public struct BaggageContext: BaggageContextProtocol {
|
|
||||||
private var _storage = [AnyBaggageContextKey: Any]()
|
|
||||||
|
|
||||||
/// Internal on purpose, please use `TODO` or `.background` to create an "empty" context,
|
|
||||||
/// which carries more meaning to other developers why an empty context was used.
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
public subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? {
|
|
||||||
get {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }))"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol BaggageContextProtocol {
|
|
||||||
/// Provides type-safe access to the baggage's values.
|
|
||||||
///
|
|
||||||
/// Rather than using this subscript directly, users are encouraged to offer a convenience accessor to their values,
|
|
||||||
/// using the following pattern:
|
|
||||||
///
|
|
||||||
/// extension BaggageContextProtocol {
|
|
||||||
/// var testID: TestIDKey.Value? {
|
|
||||||
/// get {
|
|
||||||
/// self[TestIDKey.self]
|
|
||||||
/// } set {
|
|
||||||
/// self[TestIDKey.self] = newValue
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? { get set }
|
|
||||||
|
|
||||||
/// Calls the given closure on each key/value pair in the `BaggageContext`.
|
|
||||||
///
|
|
||||||
/// - 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` 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
|
|
||||||
|
|
||||||
/// The human-readable name of this key. Defaults to `nil`.
|
|
||||||
static var name: String? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
/// A human-readable String representation of the underlying key.
|
|
||||||
/// If no explicit name has been set on the wrapped key the type name is used.
|
|
||||||
public var name: String {
|
|
||||||
return self._name ?? String(describing: self.keyType.self)
|
|
||||||
}
|
|
||||||
|
|
||||||
init<Key>(_ keyType: Key.Type) where Key: BaggageContextKey {
|
|
||||||
self.keyType = keyType
|
|
||||||
self._name = keyType.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AnyBaggageContextKey: Hashable {
|
|
||||||
public static func == (lhs: AnyBaggageContextKey, rhs: AnyBaggageContextKey) -> Bool {
|
|
||||||
return ObjectIdentifier(lhs.keyType) == ObjectIdentifier(rhs.keyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(ObjectIdentifier(self.keyType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
|
||||||
// MARK: Background BaggageContext
|
|
||||||
|
|
||||||
extension BaggageContextProtocol {
|
|
||||||
/// An empty baggage context intended as the "root" or "initial" baggage context background processing tasks, or as the "root" baggage context.
|
|
||||||
///
|
|
||||||
/// It is never canceled, has no values, and has no deadline.
|
|
||||||
/// It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.
|
|
||||||
///
|
|
||||||
/// ### Usage in frameworks and libraries
|
|
||||||
/// This function is really only intended to be used frameworks and libraries, at the "top-level" where a request's,
|
|
||||||
/// message's or task's processing is initiated. For example, a framework handling requests, should create an empty
|
|
||||||
/// context when handling a request only to immediately populate it with useful trace information extracted from e.g.
|
|
||||||
/// request headers.
|
|
||||||
///
|
|
||||||
/// ### Usage in applications
|
|
||||||
/// Application code should never have to create an empty context during the processing lifetime of any request,
|
|
||||||
/// and only should create contexts if some processing is performed in the background - thus the naming of this property.
|
|
||||||
///
|
|
||||||
/// Usually, a framework such as an HTTP server or similar "request handler" would already provide users
|
|
||||||
/// with a context to be passed along through subsequent calls.
|
|
||||||
///
|
|
||||||
/// If unsure where to obtain a context from, prefer using `.TODO("Not sure where I should get a context from here?")`,
|
|
||||||
/// such that other developers are informed that the lack of context was not done on purpose, but rather because either
|
|
||||||
/// not being sure where to obtain a context from, or other framework limitations -- e.g. the outer framework not being
|
|
||||||
/// context aware just yet.
|
|
||||||
public static var background: BaggageContext {
|
|
||||||
return BaggageContext()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
|
||||||
// MARK: "TO DO" BaggageContext
|
|
||||||
|
|
||||||
extension BaggageContextProtocol {
|
|
||||||
/// A baggage context intended as a placeholder until a real value can be passed through a function call.
|
|
||||||
///
|
|
||||||
/// It should ONLY be used while prototyping or when the passing of the proper context is not yet possible,
|
|
||||||
/// e.g. because an external library did not pass it correctly and has to be fixed before the proper context
|
|
||||||
/// can be obtained where the TO-DO is currently used.
|
|
||||||
///
|
|
||||||
/// ### Crashing on TO-DO context creation
|
|
||||||
/// You may set the `BAGGAGE_CRASH_TODOS` variable while compiling a project in order to make calls to this function crash
|
|
||||||
/// with a fatal error, indicating where a to-do baggage context was used. This comes in handy when wanting to ensure that
|
|
||||||
/// a project never ends up using with code initially was written as "was lazy, did not pass context", yet the
|
|
||||||
/// project requires context passing to be done correctly throughout the application. Similar checks can be performed
|
|
||||||
/// at compile time easily using linters (not yet implemented), since it is always valid enough to detect a to-do context
|
|
||||||
/// being passed as illegal and warn or error when spotted.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - reason: Informational reason for developers, why a placeholder context was used instead of a proper one,
|
|
||||||
/// - Returns: Empty "to-do" baggage context which should be eventually replaced with a carried through one, or `background`.
|
|
||||||
public static func TODO(_ reason: StaticString? = "", function: String = #function, file: String = #file, line: UInt = #line) -> BaggageContext {
|
|
||||||
var context = BaggageContext.background
|
|
||||||
#if BAGGAGE_CRASH_TODOS
|
|
||||||
fatalError("BAGGAGE_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)")
|
|
||||||
#else
|
|
||||||
context[TODOKey.self] = .init(file: file, line: line)
|
|
||||||
return context
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum TODOKey: BaggageContextKey {
|
|
||||||
typealias Value = TODOLocation
|
|
||||||
static var name: String? {
|
|
||||||
return "todo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Carried automatically by a "to do" baggage context.
|
|
||||||
/// It can be used to track where a context originated and which "to do" context must be fixed into a real one to avoid this.
|
|
||||||
public struct TODOLocation {
|
|
||||||
let file: String
|
|
||||||
let line: UInt
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
|
||||||
// MARK: Framework Context Protocols
|
|
||||||
|
|
||||||
/// Framework context protocols may conform to this protocol if they are used to carry a baggage object.
|
|
||||||
///
|
|
||||||
/// Notice that the baggage context property is spelled as `baggage`, this is purposefully designed in order to read well
|
|
||||||
/// with framework context's which often will be passed as `context: FrameworkContext` and used as `context.baggage`.
|
|
||||||
///
|
|
||||||
/// Such carrier protocol also conforms to `BaggageContextProtocol` meaning that it has the same convenient accessors
|
|
||||||
/// as the actual baggage type. Users should be able to use the `context.myValue` the same way if a raw baggage context,
|
|
||||||
/// or a framework context was passed around as `context` parameter, allowing for easier migrations between those two when needed.
|
|
||||||
public protocol BaggageContextCarrier: BaggageContextProtocol {
|
|
||||||
/// The underlying `BaggageContext`.
|
|
||||||
var baggage: BaggageContext { get set }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BaggageContextCarrier {
|
|
||||||
public subscript<Key: BaggageContextKey>(baggageKey: Key.Type) -> Key.Value? {
|
|
||||||
get {
|
|
||||||
return self.baggage[baggageKey]
|
|
||||||
} set {
|
|
||||||
self.baggage[baggageKey] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows {
|
|
||||||
try self.baggage.forEach(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A baggage itself also is a carrier of _itself_.
|
|
||||||
extension BaggageContext: BaggageContextCarrier {
|
|
||||||
public var baggage: BaggageContext {
|
|
||||||
get {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
/// `BaggageKey`s are used as keys in a `Baggage`. Their associated type `Value` guarantees type-safety.
|
||||||
|
/// To give your `BaggageKey` an explicit name you may override the `name` property.
|
||||||
|
///
|
||||||
|
/// In general, `BaggageKey`s should be `internal` or `private` to the part of a system using it.
|
||||||
|
///
|
||||||
|
/// All access to baggage items should be performed through an accessor computed property defined as shown below:
|
||||||
|
///
|
||||||
|
/// /// The Key type should be internal (or private).
|
||||||
|
/// enum TestIDKey: Baggage.Key {
|
||||||
|
/// typealias Value = String
|
||||||
|
/// static var nameOverride: String? { "test-id" }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// extension Baggage {
|
||||||
|
/// /// This is some useful property documentation.
|
||||||
|
/// public internal(set) var testID: String? {
|
||||||
|
/// get {
|
||||||
|
/// self[TestIDKey.self]
|
||||||
|
/// }
|
||||||
|
/// set {
|
||||||
|
/// self[TestIDKey.self] = newValue
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// This pattern allows library authors fine-grained control over which values may be set, and whic only get by end-users.
|
||||||
|
public protocol BaggageKey {
|
||||||
|
/// The type of `Value` uniquely identified by this key.
|
||||||
|
associatedtype Value
|
||||||
|
|
||||||
|
/// The human-readable name of this key.
|
||||||
|
/// This name will be used instead of the type name when a value is printed.
|
||||||
|
///
|
||||||
|
/// It MAY also be picked up by an instrument (from Swift Tracing) which serializes baggage items and e.g. used as
|
||||||
|
/// header name for carried metadata. Though generally speaking header names are NOT required to use the nameOverride,
|
||||||
|
/// and MAY use their well known names for header names etc, as it depends on the specific transport and instrument used.
|
||||||
|
///
|
||||||
|
/// For example, a baggage key representing the W3C "trace-state" header may want to return "trace-state" here,
|
||||||
|
/// in order to achieve a consistent look and feel of this baggage item throughout logging and tracing systems.
|
||||||
|
///
|
||||||
|
/// Defaults to `nil`.
|
||||||
|
static var nameOverride: String? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BaggageKey {
|
||||||
|
public static var nameOverride: String? { return nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type-erased `BaggageKey` used when iterating through the `Baggage` using its `forEach` method.
|
||||||
|
public struct AnyBaggageKey {
|
||||||
|
/// The key's type represented erased to an `Any.Type`.
|
||||||
|
public let keyType: Any.Type
|
||||||
|
|
||||||
|
private let _nameOverride: String?
|
||||||
|
|
||||||
|
/// A human-readable String representation of the underlying key.
|
||||||
|
/// If no explicit name has been set on the wrapped key the type name is used.
|
||||||
|
public var name: String {
|
||||||
|
return self._nameOverride ?? String(describing: self.keyType.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
init<Key>(_ keyType: Key.Type) where Key: BaggageKey {
|
||||||
|
self.keyType = keyType
|
||||||
|
self._nameOverride = keyType.nameOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyBaggageKey: Hashable {
|
||||||
|
public static func == (lhs: AnyBaggageKey, rhs: AnyBaggageKey) -> Bool {
|
||||||
|
return ObjectIdentifier(lhs.keyType) == ObjectIdentifier(rhs.keyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(ObjectIdentifier(self.keyType))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
@_exported import Baggage
|
||||||
|
import Logging
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Context Protocol
|
||||||
|
|
||||||
|
/// The `ContextProtocol` MAY be adopted by specific "framework contexts" such as e.g. `CoolFramework.Context` in
|
||||||
|
/// order to allow users to pass such context directly to libraries accepting any context.
|
||||||
|
///
|
||||||
|
/// This allows frameworks and library authors to offer APIs which compose more easily.
|
||||||
|
/// Please refer to the "Reference Implementation" notes on each of the requirements to know how to implement this protocol correctly.
|
||||||
|
///
|
||||||
|
/// ### Implementation notes
|
||||||
|
/// It is STRONGLY encouraged that a context type should exhibit Value Semantics (i.e. be a pure `struct`, or implement
|
||||||
|
/// the Copy-on-Write pattern), in order to implement the `set` requirements of the baggage and logger effectively,
|
||||||
|
/// and also for their user's sanity, as a reference semantics context type can be very confusing to use when shared
|
||||||
|
/// between multiple threads, as often is the case in server side environments.
|
||||||
|
///
|
||||||
|
/// It is STRONGLY encouraged to use the `DefaultContext` as inspiration for a correct implementation of a `Context`,
|
||||||
|
/// as the relationship between `Logger` and `Baggage` can be tricky to wrap your head around at first.
|
||||||
|
public protocol Context {
|
||||||
|
/// Get the `Baggage` container.
|
||||||
|
///
|
||||||
|
/// ### Implementation notes
|
||||||
|
/// Libraries and/or frameworks which conform to this protocol with their "Framework Context" types MUST
|
||||||
|
/// ensure that a modification of the baggage is properly represented in the associated `logger`. Users expect values
|
||||||
|
/// from the baggage be visible in their log statements issued via `context.logger.info()`.
|
||||||
|
///
|
||||||
|
/// Please refer to `DefaultContext`'s implementation for a reference implementation,
|
||||||
|
/// here a short snippet of how the baggage itself should be implemented:
|
||||||
|
///
|
||||||
|
/// public var baggage: Baggage {
|
||||||
|
/// willSet {
|
||||||
|
/// self._logger.updateMetadata(previous: self.baggage, latest: newValue)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #### Thread Safety
|
||||||
|
/// Implementations / MUST take care of thread-safety of modifications of the baggage. They can achieve this by such
|
||||||
|
/// context type being a pure `struct` or by implementing Copy-on-Write semantics for their type, the latter gives
|
||||||
|
/// many benefits, allowing the context to avoid being copied unless needed to (e.g. if the context type contains
|
||||||
|
/// many other values, in addition to the baggage).
|
||||||
|
var baggage: Baggage { get set }
|
||||||
|
|
||||||
|
/// The `Logger` associated with this context carrier.
|
||||||
|
///
|
||||||
|
/// It automatically populates the loggers metadata based on the `Baggage` associated with this context object.
|
||||||
|
///
|
||||||
|
/// ### Implementation notes
|
||||||
|
/// Libraries and/or frameworks which conform to this protocol with their "Framework Context" types,
|
||||||
|
/// SHOULD implement this logger by wrapping the "raw" logger associated with `_logger.with(self.baggage)` function,
|
||||||
|
/// which efficiently handles the bridging of baggage to logging metadata values.
|
||||||
|
///
|
||||||
|
/// If a new logger is set, it MUST populate itself with the latest (current) baggage of the context,
|
||||||
|
/// this is to ensure that even if users set a new logger (completely "fresh") here, the metadata from the baggage
|
||||||
|
/// still will properly be logged in other pieces of the application where the context might be passed to.
|
||||||
|
///
|
||||||
|
/// A correct implementation might look like the following:
|
||||||
|
///
|
||||||
|
/// public var _logger: Logger
|
||||||
|
/// public var logger: Logger {
|
||||||
|
/// get {
|
||||||
|
/// return self._logger
|
||||||
|
/// }
|
||||||
|
/// set {
|
||||||
|
/// self._logger = newValue
|
||||||
|
/// // Since someone could have completely replaced the logger (not just changed the log level),
|
||||||
|
/// // we have to update the baggage again, since perhaps the new logger has empty metadata.
|
||||||
|
/// self._logger.updateMetadata(previous: .topLevel, latest: self.baggage)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// #### Thread Safety
|
||||||
|
/// Implementations MUST ensure the thread-safety of mutating the logger. This is usually handled best by the
|
||||||
|
/// framework context itself being a Copy-on-Write type, however the exact safety mechanism is left up to the libraries.
|
||||||
|
var logger: Logger { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A default `Context` type.
|
||||||
|
///
|
||||||
|
/// It is a carrier of contextual `Baggage` and related `Logger`, allowing to log and trace throughout a system.
|
||||||
|
///
|
||||||
|
/// Any values set on the `baggage` will be made accessible to the logger as call-site metadata, allowing it to log those.
|
||||||
|
///
|
||||||
|
/// ### Logged Metadata and Baggage Items
|
||||||
|
///
|
||||||
|
/// Please refer to your configured log handler documentation about how to configure which metadata values should be logged
|
||||||
|
/// and which not, as each log handler may handle and configure those differently. The default implementations log *all*
|
||||||
|
/// metadata/baggage values present, which often is the right thing, however in larger systems one may want to choose a
|
||||||
|
/// log handler which allows for configuring these details.
|
||||||
|
///
|
||||||
|
/// ### Accepting context types in APIs
|
||||||
|
///
|
||||||
|
/// It is preferred to accept values of `ContextProtocol` in library APIs, as this yields a more flexible API shape,
|
||||||
|
/// to which other libraries/frameworks may pass their specific context objects.
|
||||||
|
///
|
||||||
|
/// - SeeAlso: `Baggage` from the Baggage module.
|
||||||
|
/// - SeeAlso: `Logger` from the SwiftLog package.
|
||||||
|
public struct DefaultContext: Context {
|
||||||
|
/// The `Baggage` carried with this context.
|
||||||
|
/// It's values will automatically be made available to the `logger` as metadata when logging.
|
||||||
|
///
|
||||||
|
/// Baggage values are different from plain logging metadata in that they are intended to be
|
||||||
|
/// carried across process and node boundaries (serialized and deserialized) and are made
|
||||||
|
/// available to instruments using `swift-distributed-tracing`.
|
||||||
|
public var baggage: Baggage {
|
||||||
|
willSet {
|
||||||
|
// every time the baggage changes, we need to update the logger;
|
||||||
|
// values removed from the baggage are also removed from the logger metadata.
|
||||||
|
//
|
||||||
|
// TODO: optimally, logger could some day accept baggage directly, without ever having to map it into `Metadata`,
|
||||||
|
// then we would not have to make those mappings at all and passing the logger.with(baggage) would be cheap.
|
||||||
|
//
|
||||||
|
// This implementation generally is a tradeoff, we bet on logging being performed far more often than baggage
|
||||||
|
// being changed; We do this logger update eagerly, so even if we never log anything, the logger has to be updated.
|
||||||
|
// Systems which never or rarely log will take the hit for it here. The alternative tradeoff to map lazily as `logger.with(baggage)`
|
||||||
|
// is available as well, but users would have to build their own context and specifically make use of that then -- that approach
|
||||||
|
// allows to not pay the mapping cost up front, but only if a log statement is made (but then again, the cost is paid every time we log something).
|
||||||
|
self._logger.updateMetadata(previous: self.baggage, latest: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to store the logger as `_logger` in order to avoid cyclic updates triggering when baggage changes
|
||||||
|
public var _logger: Logger
|
||||||
|
public var logger: Logger {
|
||||||
|
get {
|
||||||
|
return self._logger
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self._logger = newValue
|
||||||
|
// Since someone could have completely replaced the logger (not just changed the log level),
|
||||||
|
// we have to update the baggage again, since perhaps the new logger has empty metadata.
|
||||||
|
self._logger.updateMetadata(previous: .topLevel, latest: self.baggage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(baggage: Baggage, logger: Logger) {
|
||||||
|
self.baggage = baggage
|
||||||
|
self._logger = logger
|
||||||
|
self._logger.updateMetadata(previous: .topLevel, latest: baggage)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<C>(context: C) where C: Context {
|
||||||
|
self.baggage = context.baggage
|
||||||
|
self._logger = context.logger
|
||||||
|
self._logger.updateMetadata(previous: .topLevel, latest: self.baggage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: `with...` functions
|
||||||
|
|
||||||
|
extension DefaultContext {
|
||||||
|
/// Fluent API allowing for modification of underlying logger when passing the context to other functions.
|
||||||
|
///
|
||||||
|
/// - Parameter logger: Logger that should replace the underlying logger of this context.
|
||||||
|
/// - Returns: new context, with the passed in `logger`
|
||||||
|
public func withLogger(_ logger: Logger) -> DefaultContext {
|
||||||
|
var copy = self
|
||||||
|
copy.logger = logger
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fluent API allowing for modification of underlying logger when passing the context to other functions.
|
||||||
|
///
|
||||||
|
/// - Parameter logger: Logger that should replace the underlying logger of this context.
|
||||||
|
/// - Returns: new context, with the passed in `logger`
|
||||||
|
public func withLogger(_ function: (inout Logger) -> Void) -> DefaultContext {
|
||||||
|
var logger = self.logger
|
||||||
|
function(&logger)
|
||||||
|
return .init(baggage: self.baggage, logger: logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fluent API allowing for modification of underlying log level when passing the context to other functions.
|
||||||
|
///
|
||||||
|
/// - Parameter logLevel: New log level which should be used to create the new context
|
||||||
|
/// - Returns: new context, with the passed in `logLevel` used for the underlying logger
|
||||||
|
public func withLogLevel(_ logLevel: Logger.Level) -> DefaultContext {
|
||||||
|
var copy = self
|
||||||
|
copy.logger.logLevel = logLevel
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fluent API allowing for modification a few baggage values when passing the context to other functions, e.g.
|
||||||
|
///
|
||||||
|
/// makeRequest(url, context: context.withBaggage {
|
||||||
|
/// $0.traceID = "fake-value"
|
||||||
|
/// $0.calledFrom = #function
|
||||||
|
/// })
|
||||||
|
///
|
||||||
|
/// - Parameter function:
|
||||||
|
public func withBaggage(_ function: (inout Baggage) -> Void) -> DefaultContext {
|
||||||
|
var baggage = self.baggage
|
||||||
|
function(&baggage)
|
||||||
|
return self.withBaggage(baggage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fluent API allowing for replacement of underlying baggage when passing the context to other functions.
|
||||||
|
///
|
||||||
|
/// - Warning: Use with caution, generally it is not recommended to modify an entire baggage, but rather only add a few values to it.
|
||||||
|
///
|
||||||
|
/// - Parameter baggage: baggage that should *replace* the context's current baggage.
|
||||||
|
/// - Returns: new context, with the passed in baggage
|
||||||
|
public func withBaggage(_ baggage: Baggage) -> DefaultContext {
|
||||||
|
var copy = self
|
||||||
|
copy.baggage = baggage
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Context Initializers
|
||||||
|
|
||||||
|
extension DefaultContext {
|
||||||
|
/// Creates a new empty "top level" default baggage context, generally used as an "initial" context to immediately be populated with
|
||||||
|
/// some values by a framework or runtime. Another use case is for tasks starting in the "background" (e.g. on a timer),
|
||||||
|
/// which don't have a "request context" per se that they can pick up, and as such they have to create a "top level"
|
||||||
|
/// baggage for their work.
|
||||||
|
///
|
||||||
|
/// It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.
|
||||||
|
///
|
||||||
|
/// ### Usage in frameworks and libraries
|
||||||
|
/// This function is really only intended to be used frameworks and libraries, at the "top-level" where a request's,
|
||||||
|
/// message's or task's processing is initiated. For example, a framework handling requests, should create an empty
|
||||||
|
/// context when handling a request only to immediately populate it with useful trace information extracted from e.g.
|
||||||
|
/// request headers.
|
||||||
|
///
|
||||||
|
/// ### Usage in applications
|
||||||
|
/// Application code should never have to create an empty context during the processing lifetime of any request,
|
||||||
|
/// and only should create contexts if some processing is performed in the background - thus the naming of this property.
|
||||||
|
///
|
||||||
|
/// Usually, a framework such as an HTTP server or similar "request handler" would already provide users
|
||||||
|
/// with a context to be passed along through subsequent calls.
|
||||||
|
///
|
||||||
|
/// If unsure where to obtain a context from, prefer using `.TODO("Not sure where I should get a context from here?")`,
|
||||||
|
/// such that other developers are informed that the lack of context was not done on purpose, but rather because either
|
||||||
|
/// not being sure where to obtain a context from, or other framework limitations -- e.g. the outer framework not being
|
||||||
|
/// context aware just yet.
|
||||||
|
public static func topLevel(logger: Logger) -> DefaultContext {
|
||||||
|
return .init(baggage: .topLevel, logger: logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DefaultContext {
|
||||||
|
/// A baggage context intended as a placeholder until a real value can be passed through a function call.
|
||||||
|
///
|
||||||
|
/// It should ONLY be used while prototyping or when the passing of the proper context is not yet possible,
|
||||||
|
/// e.g. because an external library did not pass it correctly and has to be fixed before the proper context
|
||||||
|
/// can be obtained where the TO-DO is currently used.
|
||||||
|
///
|
||||||
|
/// ## Crashing on TO-DO context creation
|
||||||
|
/// You may set the `BAGGAGE_CRASH_TODOS` variable while compiling a project in order to make calls to this function crash
|
||||||
|
/// with a fatal error, indicating where a to-do baggage context was used. This comes in handy when wanting to ensure that
|
||||||
|
/// a project never ends up using with code initially was written as "was lazy, did not pass context", yet the
|
||||||
|
/// project requires context passing to be done correctly throughout the application. Similar checks can be performed
|
||||||
|
/// at compile time easily using linters (not yet implemented), since it is always valid enough to detect a to-do context
|
||||||
|
/// being passed as illegal and warn or error when spotted.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// frameworkHandler { what in
|
||||||
|
/// hello(who: "World", baggage: .TODO(logger: logger, "The framework XYZ should be modified to pass us a context here, and we'd pass it along"))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - reason: Informational reason for developers, why a placeholder context was used instead of a proper one,
|
||||||
|
/// - Returns: Empty "to-do" baggage context which should be eventually replaced with a carried through one, or `background`.
|
||||||
|
public static func TODO(logger: Logger, _ reason: StaticString? = "", function: String = #function, file: String = #file, line: UInt = #line) -> DefaultContext {
|
||||||
|
let baggage = Baggage.TODO(reason, function: function, file: file, line: line)
|
||||||
|
#if BAGGAGE_CRASH_TODOS
|
||||||
|
fatalError("BAGGAGE_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)", file: file, line: line)
|
||||||
|
#else
|
||||||
|
return .init(baggage: baggage, logger: logger)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
import Baggage
|
||||||
|
import Logging
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Logger with Baggage
|
||||||
|
|
||||||
|
extension Logger {
|
||||||
|
/// Returns a logger that in addition to any explicit metadata passed to log statements,
|
||||||
|
/// also includes the `Baggage` adapted into metadata values.
|
||||||
|
///
|
||||||
|
/// The rendering of baggage values into metadata values is performed on demand,
|
||||||
|
/// whenever a log statement is effective (i.e. will be logged, according to active `logLevel`).
|
||||||
|
///
|
||||||
|
/// Note that when it is known that multiple log statements will be performed with the baggage it is preferable to
|
||||||
|
/// modify the logger's metadata by issuing `logger.updateMetadata(previous: baggage, latest: baggage)` instead.
|
||||||
|
///
|
||||||
|
/// - SeeAlso:
|
||||||
|
public func with(_ baggage: Baggage) -> Logger {
|
||||||
|
return Logger(
|
||||||
|
label: self.label,
|
||||||
|
factory: { _ in BaggageMetadataLogHandler(logger: self, baggage: baggage) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the logger's metadata in accordance to the passed in baggage.
|
||||||
|
///
|
||||||
|
/// Items which were previously present in the baggage but are now removed will also be removed from the logger's
|
||||||
|
/// metadata.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - previous: baggage which was previously used to set metadata on this logger (pass `.topLevel` if unknown or none)
|
||||||
|
/// - latest: the current, latest, state of the baggage container; all of it's loggable values will be set as the `Logger`'s metadata
|
||||||
|
public mutating func updateMetadata(previous: Baggage, latest: Baggage) {
|
||||||
|
var removedKeys: Set<AnyBaggageKey> = []
|
||||||
|
removedKeys.reserveCapacity(previous.count)
|
||||||
|
previous.forEach { key, _ in
|
||||||
|
removedKeys.insert(key)
|
||||||
|
}
|
||||||
|
latest.forEach { key, value in
|
||||||
|
removedKeys.remove(key)
|
||||||
|
if let convertible = value as? String {
|
||||||
|
self[metadataKey: key.name] = .string(convertible)
|
||||||
|
} else if let convertible = value as? CustomStringConvertible {
|
||||||
|
self[metadataKey: key.name] = .stringConvertible(convertible)
|
||||||
|
} else {
|
||||||
|
self[metadataKey: key.name] = .stringConvertible(BaggageValueCustomStringConvertible(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removedKeys.forEach { removedKey in
|
||||||
|
self[metadataKey: removedKey.name] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Baggage (as additional Logger.Metadata) LogHandler
|
||||||
|
|
||||||
|
/// Proxying log handler which adds `Baggage` as metadata when log events are to be emitted.
|
||||||
|
///
|
||||||
|
/// The values stored in the `Baggage` are merged with the existing metadata on the logger. If both contain values for the same key,
|
||||||
|
/// the `Baggage` values are preferred.
|
||||||
|
public struct BaggageMetadataLogHandler: LogHandler {
|
||||||
|
private var underlying: Logger
|
||||||
|
private let baggage: Baggage
|
||||||
|
|
||||||
|
public init(logger underlying: Logger, baggage: Baggage) {
|
||||||
|
self.underlying = underlying
|
||||||
|
self.baggage = baggage
|
||||||
|
}
|
||||||
|
|
||||||
|
public var logLevel: Logger.Level {
|
||||||
|
get {
|
||||||
|
return self.underlying.logLevel
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.underlying.logLevel = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func log(
|
||||||
|
level: Logger.Level,
|
||||||
|
message: Logger.Message,
|
||||||
|
metadata: Logger.Metadata?,
|
||||||
|
source: String,
|
||||||
|
file: String,
|
||||||
|
function: String,
|
||||||
|
line: UInt
|
||||||
|
) {
|
||||||
|
guard self.underlying.logLevel <= level else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveMetadata = self.baggageAsMetadata()
|
||||||
|
if let metadata = metadata {
|
||||||
|
effectiveMetadata.merge(metadata, uniquingKeysWith: { _, r in r })
|
||||||
|
}
|
||||||
|
self.underlying.log(level: level, message, metadata: effectiveMetadata, source: source, file: file, function: function, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var metadata: Logger.Metadata {
|
||||||
|
get {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
newValue.forEach { k, v in
|
||||||
|
self.underlying[metadataKey: k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note that this does NOT look up inside the baggage.
|
||||||
|
///
|
||||||
|
/// This is because a context lookup either has to use the specific type key, or iterate over all keys to locate one by name,
|
||||||
|
/// which may be incorrect still, thus rather than making an potentially slightly incorrect lookup, we do not implement peeking
|
||||||
|
/// into a baggage with String keys through this handler (as that is not a capability `Baggage` offers in any case.
|
||||||
|
public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
|
||||||
|
get {
|
||||||
|
return self.underlying[metadataKey: metadataKey]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.underlying[metadataKey: metadataKey] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func baggageAsMetadata() -> Logger.Metadata {
|
||||||
|
var effectiveMetadata: Logger.Metadata = [:]
|
||||||
|
self.baggage.forEach { key, value in
|
||||||
|
if let convertible = value as? String {
|
||||||
|
effectiveMetadata[key.name] = .string(convertible)
|
||||||
|
} else if let convertible = value as? CustomStringConvertible {
|
||||||
|
effectiveMetadata[key.name] = .stringConvertible(convertible)
|
||||||
|
} else {
|
||||||
|
effectiveMetadata[key.name] = .stringConvertible(BaggageValueCustomStringConvertible(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return effectiveMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BaggageValueCustomStringConvertible: CustomStringConvertible {
|
||||||
|
let value: Any
|
||||||
|
|
||||||
|
init(_ value: Any) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "\(self.value)"
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,4 +25,7 @@ public enum BenchmarkCategory: String {
|
||||||
|
|
||||||
// Explicit skip marker
|
// Explicit skip marker
|
||||||
case skip
|
case skip
|
||||||
|
|
||||||
|
// --- custom ---
|
||||||
|
case logging
|
||||||
}
|
}
|
|
@ -0,0 +1,379 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
import Baggage
|
||||||
|
import BaggageContext
|
||||||
|
import BaggageContextBenchmarkTools
|
||||||
|
import Dispatch
|
||||||
|
import class Foundation.NSLock
|
||||||
|
import Logging
|
||||||
|
|
||||||
|
private let message: Logger.Message = "Hello world how are you"
|
||||||
|
|
||||||
|
func pad(_ label: String) -> String {
|
||||||
|
return "\(label)\(String(repeating: " ", count: max(0, 80 - label.count)))"
|
||||||
|
}
|
||||||
|
|
||||||
|
public let BaggageLoggingBenchmarks: [BenchmarkInfo] = [
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Baseline
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.0_log_noop_baseline_empty"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "0_log_noop_baseline_empty", factory: { _ in SwiftLogNoOpLogHandler() })
|
||||||
|
log_baseline(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.0_log_noop_baseline_smallMetadata"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var logger = Logger(label: "0_log_noop_baseline_smallMetadata", factory: { _ in SwiftLogNoOpLogHandler() })
|
||||||
|
logger[metadataKey: "k1"] = "k1-value"
|
||||||
|
logger[metadataKey: "k2"] = "k2-value"
|
||||||
|
logger[metadataKey: "k3"] = "k3-value"
|
||||||
|
log_baseline(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Context / Baggage (Really do log)
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.0_log_noop_loggerWithBaggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "0_log_noop_loggerWithBaggage_small", factory: { _ in SwiftLogNoOpLogHandler() })
|
||||||
|
var baggage = Baggage.topLevel
|
||||||
|
baggage[TestK1.self] = "k1-value"
|
||||||
|
baggage[TestK2.self] = "k2-value"
|
||||||
|
baggage[TestK3.self] = "k3-value"
|
||||||
|
log_loggerWithBaggage(logger: logger, baggage: baggage, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.0_log_noop_context_with_baggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var context = DefaultContext.topLevel(logger: Logger(label: "0_log_noop_context_with_baggage_small", factory: { _ in SwiftLogNoOpLogHandler() }))
|
||||||
|
context.baggage[TestK1.self] = "k1-value"
|
||||||
|
context.baggage[TestK2.self] = "k2-value"
|
||||||
|
context.baggage[TestK3.self] = "k3-value"
|
||||||
|
log_throughContext(context: context, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Context / Baggage (do actually emit the logs)
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.1_log_real_baseline_empty"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "1_log_real_baseline_empty", factory: StreamLogHandler.standardError)
|
||||||
|
log_baseline(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.1_log_real_baseline_smallMetadata"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var logger = Logger(label: "1_log_real_baseline_smallMetadata", factory: StreamLogHandler.standardError)
|
||||||
|
logger[metadataKey: "k1"] = "k1-value"
|
||||||
|
logger[metadataKey: "k2"] = "k2-value"
|
||||||
|
logger[metadataKey: "k3"] = "k3-value"
|
||||||
|
log_baseline(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.1_log_real_loggerWithBaggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "1_log_real_loggerWithBaggage_small", factory: StreamLogHandler.standardError)
|
||||||
|
var baggage = Baggage.topLevel
|
||||||
|
baggage[TestK1.self] = "k1-value"
|
||||||
|
baggage[TestK2.self] = "k2-value"
|
||||||
|
baggage[TestK3.self] = "k3-value"
|
||||||
|
log_loggerWithBaggage(logger: logger, baggage: baggage, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.1_log_real_context_with_baggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var context = DefaultContext.topLevel(logger: Logger(label: "1_log_real_context_with_baggage_small", factory: StreamLogHandler.standardError))
|
||||||
|
context.baggage[TestK1.self] = "k1-value"
|
||||||
|
context.baggage[TestK2.self] = "k2-value"
|
||||||
|
context.baggage[TestK3.self] = "k3-value"
|
||||||
|
log_throughContext(context: context, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Context / Baggage (log not emitted because logLevel)
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.2_log_real-trace_baseline_empty"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "trace_baseline_empty", factory: StreamLogHandler.standardError)
|
||||||
|
log_baseline_trace(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.2_log_real-trace_baseline_smallMetadata"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var logger = Logger(label: "2_log_real-trace_baseline_smallMetadata", factory: StreamLogHandler.standardError)
|
||||||
|
logger[metadataKey: "k1"] = "k1-value"
|
||||||
|
logger[metadataKey: "k2"] = "k2-value"
|
||||||
|
logger[metadataKey: "k3"] = "k3-value"
|
||||||
|
log_baseline_trace(logger: logger, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.2_log_real-trace_loggerWithBaggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
let logger = Logger(label: "2_log_real-trace_loggerWithBaggage_small", factory: StreamLogHandler.standardError)
|
||||||
|
var baggage = Baggage.topLevel
|
||||||
|
baggage[TestK1.self] = "k1-value"
|
||||||
|
baggage[TestK2.self] = "k2-value"
|
||||||
|
baggage[TestK3.self] = "k3-value"
|
||||||
|
log_loggerWithBaggage_trace(logger: logger, baggage: baggage, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.2_log_real-trace_context_with_baggage_small"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var context = DefaultContext.topLevel(logger: Logger(label: "2_log_real-trace_context_with_baggage_small", factory: StreamLogHandler.standardError))
|
||||||
|
context.baggage[TestK1.self] = "k1-value"
|
||||||
|
context.baggage[TestK2.self] = "k2-value"
|
||||||
|
context.baggage[TestK3.self] = "k3-value"
|
||||||
|
log_throughContext_trace(context: context, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: materialize once once
|
||||||
|
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.3_log_real_small_context_materializeOnce"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var context = DefaultContext.topLevel(logger: Logger(label: "3_log_real_context_materializeOnce", factory: StreamLogHandler.standardError))
|
||||||
|
context.baggage[TestK1.self] = "k1-value"
|
||||||
|
context.baggage[TestK2.self] = "k2-value"
|
||||||
|
context.baggage[TestK3.self] = "k3-value"
|
||||||
|
log_materializeOnce(context: context, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
BenchmarkInfo(
|
||||||
|
name: pad("BaggageLoggingBenchmarks.3_log_real-trace_small_context_materializeOnce"),
|
||||||
|
runFunction: { iters in
|
||||||
|
var context = DefaultContext.topLevel(logger: Logger(label: "3_log_real_context_materializeOnce", factory: StreamLogHandler.standardError))
|
||||||
|
context.baggage[TestK1.self] = "k1-value"
|
||||||
|
context.baggage[TestK2.self] = "k2-value"
|
||||||
|
context.baggage[TestK3.self] = "k3-value"
|
||||||
|
log_materializeOnce_trace(context: context, iters: iters)
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
.logging,
|
||||||
|
],
|
||||||
|
setUpFunction: { setUp() },
|
||||||
|
tearDownFunction: tearDown
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
private func setUp() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tearDown() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Benchmarks
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_baseline(logger: Logger, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.warning(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_baseline_trace(logger: Logger, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.trace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_loggerWithBaggage(logger: Logger, baggage: Baggage, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.with(baggage).warning(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_throughContext(context: Context, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
context.logger.warning(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_loggerWithBaggage_trace(logger: Logger, baggage: Baggage, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.with(baggage).trace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_throughContext_trace(context: Context, iters remaining: Int) {
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
context.logger.trace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_materializeOnce_trace(context: Context, iters remaining: Int) {
|
||||||
|
var logger = context.logger
|
||||||
|
context.baggage.forEach { key, value in
|
||||||
|
logger[metadataKey: key.name] = "\(value)"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.trace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
func log_materializeOnce(context: Context, iters remaining: Int) {
|
||||||
|
var logger = context.logger
|
||||||
|
context.baggage.forEach { key, value in
|
||||||
|
logger[metadataKey: key.name] = "\(value)"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0 ..< remaining {
|
||||||
|
logger.warning(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
|
// MARK: Baggage Keys
|
||||||
|
|
||||||
|
private enum TestK1: BaggageKey {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestK2: BaggageKey {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestK3: BaggageKey {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestK4: BaggageKey {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestKD1: BaggageKey {
|
||||||
|
typealias Value = [String: String]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
fileprivate var k1: TestK1.Value? {
|
||||||
|
get { return self[TestK1.self] }
|
||||||
|
set { self[TestK1.self] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var k2: TestK2.Value? {
|
||||||
|
get { return self[TestK2.self] }
|
||||||
|
set { self[TestK2.self] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var k3: TestK3.Value? {
|
||||||
|
get { return self[TestK3.self] }
|
||||||
|
set { self[TestK3.self] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var k4: TestK4.Value? {
|
||||||
|
get { return self[TestK4.self] }
|
||||||
|
set { self[TestK4.self] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate var kd1: TestKD1.Value? {
|
||||||
|
get { return self[TestKD1.self] }
|
||||||
|
set { self[TestKD1.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,16 +12,17 @@
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import Baggage
|
import Baggage
|
||||||
import BaggageBenchmarkTools
|
import BaggageContextBenchmarkTools
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import class Foundation.NSLock
|
import class Foundation.NSLock
|
||||||
|
|
||||||
public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
||||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
// MARK: "Read only" context passing around
|
// MARK: "Read only" context passing around
|
||||||
BenchmarkInfo(
|
BenchmarkInfo(
|
||||||
name: "BaggagePassingBenchmarks.pass_async_empty_100_000 ",
|
name: "BaggagePassingBenchmarks.pass_async_empty_100_000 ",
|
||||||
runFunction: { _ in
|
runFunction: { _ in
|
||||||
let context = BaggageContext.background
|
let context = Baggage.topLevel
|
||||||
pass_async(context: context, times: 100_000)
|
pass_async(context: context, times: 100_000)
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
@ -31,7 +32,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
||||||
BenchmarkInfo(
|
BenchmarkInfo(
|
||||||
name: "BaggagePassingBenchmarks.pass_async_smol_100_000 ",
|
name: "BaggagePassingBenchmarks.pass_async_smol_100_000 ",
|
||||||
runFunction: { _ in
|
runFunction: { _ in
|
||||||
var context = BaggageContext.background
|
var context = Baggage.topLevel
|
||||||
context.k1 = "one"
|
context.k1 = "one"
|
||||||
context.k2 = "two"
|
context.k2 = "two"
|
||||||
context.k3 = "three"
|
context.k3 = "three"
|
||||||
|
@ -45,7 +46,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
||||||
BenchmarkInfo(
|
BenchmarkInfo(
|
||||||
name: "BaggagePassingBenchmarks.pass_async_small_nonconst_100_000",
|
name: "BaggagePassingBenchmarks.pass_async_small_nonconst_100_000",
|
||||||
runFunction: { _ in
|
runFunction: { _ in
|
||||||
var context = BaggageContext.background
|
var context = Baggage.topLevel
|
||||||
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
|
@ -65,7 +66,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
||||||
BenchmarkInfo(
|
BenchmarkInfo(
|
||||||
name: "BaggagePassingBenchmarks.pass_mut_async_small_100_000 ",
|
name: "BaggagePassingBenchmarks.pass_mut_async_small_100_000 ",
|
||||||
runFunction: { _ in
|
runFunction: { _ in
|
||||||
var context = BaggageContext.background
|
var context = Baggage.topLevel
|
||||||
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
||||||
|
@ -87,10 +88,10 @@ private func tearDown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@inline(never)
|
@inline(never)
|
||||||
func pass_async(context: BaggageContext, times remaining: Int) {
|
func pass_async(context: Baggage, times remaining: Int) {
|
||||||
let latch = CountDownLatch(from: 1)
|
let latch = CountDownLatch(from: 1)
|
||||||
|
|
||||||
func pass_async0(context: BaggageContext, times remaining: Int) {
|
func pass_async0(context: Baggage, times remaining: Int) {
|
||||||
if remaining == 0 {
|
if remaining == 0 {
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
|
@ -105,11 +106,11 @@ func pass_async(context: BaggageContext, times remaining: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@inline(never)
|
@inline(never)
|
||||||
func pass_mut_async(context: BaggageContext, times remaining: Int) {
|
func pass_mut_async(context: Baggage, times remaining: Int) {
|
||||||
var context = context
|
var context = context
|
||||||
let latch = CountDownLatch(from: 1)
|
let latch = CountDownLatch(from: 1)
|
||||||
|
|
||||||
func pass_async0(context: BaggageContext, times remaining: Int) {
|
func pass_async0(context: Baggage, times remaining: Int) {
|
||||||
if remaining == 0 {
|
if remaining == 0 {
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
|
@ -132,31 +133,31 @@ func pass_mut_async(context: BaggageContext, times remaining: Int) {
|
||||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||||
// MARK: Baggage Keys
|
// MARK: Baggage Keys
|
||||||
|
|
||||||
private enum TestPassCounterKey: BaggageContextKey {
|
private enum TestPassCounterKey: BaggageKey {
|
||||||
typealias Value = Int
|
typealias Value = Int
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestK1: BaggageContextKey {
|
private enum TestK1: BaggageKey {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestK2: BaggageContextKey {
|
private enum TestK2: BaggageKey {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestK3: BaggageContextKey {
|
private enum TestK3: BaggageKey {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestK4: BaggageContextKey {
|
private enum TestK4: BaggageKey {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestKD1: BaggageContextKey {
|
private enum TestKD1: BaggageKey {
|
||||||
typealias Value = [String: String]
|
typealias Value = [String: String]
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BaggageContext {
|
extension Baggage {
|
||||||
fileprivate var passCounter: TestPassCounterKey.Value {
|
fileprivate var passCounter: TestPassCounterKey.Value {
|
||||||
get { return self[TestPassCounterKey.self] ?? 0 }
|
get { return self[TestPassCounterKey.self] ?? 0 }
|
||||||
set { self[TestPassCounterKey.self] = newValue }
|
set { self[TestPassCounterKey.self] = newValue }
|
|
@ -11,7 +11,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
import BaggageBenchmarkTools
|
import BaggageContextBenchmarkTools
|
||||||
|
|
||||||
assert({
|
assert({
|
||||||
print("===========================================================================")
|
print("===========================================================================")
|
||||||
|
@ -37,5 +37,6 @@ private func registerBenchmark(_ name: String, _ function: @escaping (Int) -> Vo
|
||||||
}
|
}
|
||||||
|
|
||||||
registerBenchmark(BaggagePassingBenchmarks)
|
registerBenchmark(BaggagePassingBenchmarks)
|
||||||
|
registerBenchmark(BaggageLoggingBenchmarks)
|
||||||
|
|
||||||
main()
|
main()
|
|
@ -1,113 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
import Baggage
|
|
||||||
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 {
|
|
||||||
private var underlying: Logger
|
|
||||||
private let context: BaggageContext
|
|
||||||
|
|
||||||
public init(logger underlying: Logger, context: BaggageContext) {
|
|
||||||
self.underlying = underlying
|
|
||||||
self.context = context
|
|
||||||
}
|
|
||||||
|
|
||||||
public var logLevel: Logger.Level {
|
|
||||||
get {
|
|
||||||
return self.underlying.logLevel
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.underlying.logLevel = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func log(
|
|
||||||
level: Logger.Level,
|
|
||||||
message: Logger.Message,
|
|
||||||
metadata: Logger.Metadata?,
|
|
||||||
source: String,
|
|
||||||
file: String,
|
|
||||||
function: String,
|
|
||||||
line: UInt
|
|
||||||
) {
|
|
||||||
guard self.underlying.logLevel <= level else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var effectiveMetadata = self.baggageAsMetadata()
|
|
||||||
if let metadata = metadata {
|
|
||||||
effectiveMetadata.merge(metadata, uniquingKeysWith: { _, r in r })
|
|
||||||
}
|
|
||||||
self.underlying.log(level: level, message, metadata: effectiveMetadata, source: source, file: file, function: function, line: line)
|
|
||||||
}
|
|
||||||
|
|
||||||
public var metadata: Logger.Metadata {
|
|
||||||
get {
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
newValue.forEach { k, v in
|
|
||||||
self.underlying[metadataKey: k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Note that this does NOT look up inside the baggage.
|
|
||||||
///
|
|
||||||
/// This is because a context lookup either has to use the specific type key, or iterate over all keys to locate one by name,
|
|
||||||
/// which may be incorrect still, thus rather than making an potentially slightly incorrect lookup, we do not implement peeking
|
|
||||||
/// into a baggage with String keys through this handler (as that is not a capability `BaggageContext` offers in any case.
|
|
||||||
public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
|
|
||||||
get {
|
|
||||||
return self.underlying[metadataKey: metadataKey]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.underlying[metadataKey: metadataKey] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func baggageAsMetadata() -> Logger.Metadata {
|
|
||||||
var effectiveMetadata: Logger.Metadata = [:]
|
|
||||||
self.context.forEach { key, value in
|
|
||||||
if let convertible = value as? String {
|
|
||||||
effectiveMetadata[key.name] = .string(convertible)
|
|
||||||
} else if let convertible = value as? CustomStringConvertible {
|
|
||||||
effectiveMetadata[key.name] = .stringConvertible(convertible)
|
|
||||||
} else {
|
|
||||||
effectiveMetadata[key.name] = .stringConvertible(BaggageValueCustomStringConvertible(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return effectiveMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BaggageValueCustomStringConvertible: CustomStringConvertible {
|
|
||||||
let value: Any
|
|
||||||
|
|
||||||
init(_ value: Any) {
|
|
||||||
self.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "\(self.value)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
import Baggage
|
|
||||||
import Logging
|
|
||||||
|
|
||||||
extension Logger {
|
|
||||||
/// Returns a logger that in addition to any explicit metadata passed to log statements,
|
|
||||||
/// also includes the `BaggageContext` adapted into metadata values.
|
|
||||||
///
|
|
||||||
/// The rendering of baggage values into metadata values is performed on demand,
|
|
||||||
/// whenever a log statement is effective (i.e. will be logged, according to active `logLevel`).
|
|
||||||
public func with(context: BaggageContext) -> Logger {
|
|
||||||
return Logger(
|
|
||||||
label: self.label,
|
|
||||||
factory: { _ in BaggageMetadataLogHandler(logger: self, context: context) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
import Baggage
|
|
||||||
import Logging
|
|
||||||
|
|
||||||
/// 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 context carrier.
|
|
||||||
///
|
|
||||||
/// It should automatically populate the loggers metadata based on the `BaggageContext` associated with this context object.
|
|
||||||
///
|
|
||||||
/// ### Implementation note
|
|
||||||
///
|
|
||||||
/// Libraries and/or frameworks which conform to this protocol with their "Framework Context" types,
|
|
||||||
/// 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 }
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
//
|
//
|
||||||
// LoggingBaggageContextCarrierTests+XCTest.swift
|
// BaggageContextTests+XCTest.swift
|
||||||
//
|
//
|
||||||
import XCTest
|
import XCTest
|
||||||
///
|
///
|
||||||
|
@ -20,14 +20,15 @@ import XCTest
|
||||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||||
///
|
///
|
||||||
|
|
||||||
extension LoggingBaggageContextCarrierTests {
|
extension BaggageContextTests {
|
||||||
|
|
||||||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
||||||
static var allTests : [(String, (LoggingBaggageContextCarrierTests) -> () throws -> Void)] {
|
static var allTests : [(String, (BaggageContextTests) -> () throws -> Void)] {
|
||||||
return [
|
return [
|
||||||
("test_ContextWithLogger_dumpBaggage", test_ContextWithLogger_dumpBaggage),
|
("test_ExampleFrameworkContext_dumpBaggage", test_ExampleFrameworkContext_dumpBaggage),
|
||||||
("test_ContextWithLogger_log_withBaggage", test_ContextWithLogger_log_withBaggage),
|
("test_ExampleFrameworkContext_log_withBaggage", test_ExampleFrameworkContext_log_withBaggage),
|
||||||
("test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata", test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata),
|
("test_DefaultContext_log_withBaggage", test_DefaultContext_log_withBaggage),
|
||||||
|
("test_ExampleFrameworkContext_log_prefersBaggageContextOverExistingLoggerMetadata", test_ExampleFrameworkContext_log_prefersBaggageContextOverExistingLoggerMetadata),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
import Baggage
|
||||||
|
import BaggageContext
|
||||||
|
import Logging
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class BaggageContextTests: XCTestCase {
|
||||||
|
func test_ExampleFrameworkContext_dumpBaggage() throws {
|
||||||
|
var baggage = Baggage.topLevel
|
||||||
|
let logger = Logger(label: "TheLogger")
|
||||||
|
|
||||||
|
baggage.testID = 42
|
||||||
|
let context = ExampleFrameworkContext(context: baggage, logger: logger)
|
||||||
|
|
||||||
|
func frameworkFunctionDumpsBaggage(param: String, context: Context) -> String {
|
||||||
|
var s = ""
|
||||||
|
context.baggage.forEach { key, item in
|
||||||
|
s += "\(key.name): \(item)\n"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = frameworkFunctionDumpsBaggage(param: "x", context: context)
|
||||||
|
XCTAssertEqual(
|
||||||
|
result,
|
||||||
|
"""
|
||||||
|
TestIDKey: 42
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_ExampleFrameworkContext_log_withBaggage() throws {
|
||||||
|
let baggage = Baggage.topLevel
|
||||||
|
let logging = TestLogging()
|
||||||
|
let logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
|
||||||
|
|
||||||
|
var context = ExampleFrameworkContext(context: baggage, logger: logger)
|
||||||
|
|
||||||
|
context.baggage.secondTestID = "value"
|
||||||
|
context.baggage.testID = 42
|
||||||
|
context.logger.info("Hello")
|
||||||
|
|
||||||
|
context.baggage.testID = nil
|
||||||
|
context.logger.warning("World")
|
||||||
|
|
||||||
|
context.baggage.secondTestID = nil
|
||||||
|
context.logger[metadataKey: "metadata"] = "on-logger"
|
||||||
|
context.logger.warning("!")
|
||||||
|
|
||||||
|
// These implicitly exercise logger.updateMetadata
|
||||||
|
|
||||||
|
logging.history.assertExist(level: .info, message: "Hello", metadata: [
|
||||||
|
"TestIDKey": .stringConvertible(42),
|
||||||
|
"secondIDExplicitlyNamed": "value",
|
||||||
|
])
|
||||||
|
logging.history.assertExist(level: .warning, message: "World", metadata: [
|
||||||
|
"secondIDExplicitlyNamed": "value",
|
||||||
|
])
|
||||||
|
logging.history.assertExist(level: .warning, message: "!", metadata: [
|
||||||
|
"metadata": "on-logger",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_DefaultContext_log_withBaggage() throws {
|
||||||
|
let logging = TestLogging()
|
||||||
|
let logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
|
||||||
|
|
||||||
|
var context = DefaultContext.topLevel(logger: logger)
|
||||||
|
|
||||||
|
context.baggage.secondTestID = "value"
|
||||||
|
context.baggage.testID = 42
|
||||||
|
context.logger.info("Hello")
|
||||||
|
|
||||||
|
context.baggage.testID = nil
|
||||||
|
context.logger.warning("World")
|
||||||
|
|
||||||
|
context.baggage.secondTestID = nil
|
||||||
|
context.logger[metadataKey: "metadata"] = "on-logger"
|
||||||
|
context.logger.warning("!")
|
||||||
|
|
||||||
|
// These implicitly exercise logger.updateMetadata
|
||||||
|
|
||||||
|
logging.history.assertExist(level: .info, message: "Hello", metadata: [
|
||||||
|
"TestIDKey": .stringConvertible(42),
|
||||||
|
"secondIDExplicitlyNamed": "value",
|
||||||
|
])
|
||||||
|
logging.history.assertExist(level: .warning, message: "World", metadata: [
|
||||||
|
"secondIDExplicitlyNamed": "value",
|
||||||
|
])
|
||||||
|
logging.history.assertExist(level: .warning, message: "!", metadata: [
|
||||||
|
"metadata": "on-logger",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_ExampleFrameworkContext_log_prefersBaggageContextOverExistingLoggerMetadata() {
|
||||||
|
let baggage = Baggage.topLevel
|
||||||
|
let logging = TestLogging()
|
||||||
|
var logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
|
||||||
|
logger[metadataKey: "secondIDExplicitlyNamed"] = "set on logger"
|
||||||
|
|
||||||
|
var context = ExampleFrameworkContext(context: baggage, logger: logger)
|
||||||
|
|
||||||
|
context.baggage.secondTestID = "set on baggage"
|
||||||
|
|
||||||
|
context.logger.info("Hello")
|
||||||
|
|
||||||
|
logging.history.assertExist(level: .info, message: "Hello", metadata: [
|
||||||
|
"secondIDExplicitlyNamed": "set on baggage",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExampleFrameworkContext: BaggageContext.Context {
|
||||||
|
var baggage: Baggage {
|
||||||
|
willSet {
|
||||||
|
self._logger.updateMetadata(previous: self.baggage, latest: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _logger: Logger
|
||||||
|
var logger: Logger {
|
||||||
|
get {
|
||||||
|
return self._logger
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self._logger = newValue
|
||||||
|
self._logger.updateMetadata(previous: self.baggage, latest: self.baggage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(context baggage: Baggage, logger: Logger) {
|
||||||
|
self.baggage = baggage
|
||||||
|
self._logger = logger
|
||||||
|
self._logger.updateMetadata(previous: .topLevel, latest: baggage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CoolFrameworkContext: BaggageContext.Context {
|
||||||
|
var baggage: Baggage {
|
||||||
|
willSet {
|
||||||
|
self.logger.updateMetadata(previous: self.baggage, latest: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger: Logger {
|
||||||
|
didSet {
|
||||||
|
self.logger.updateMetadata(previous: self.baggage, latest: self.baggage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// framework context defines other values as well
|
||||||
|
let frameworkField: String
|
||||||
|
|
||||||
|
// including the popular eventLoop
|
||||||
|
let eventLoop: FakeEventLoop
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.baggage = .topLevel
|
||||||
|
self.logger = Logger(label: "some-framework-logger")
|
||||||
|
self.eventLoop = FakeEventLoop()
|
||||||
|
self.frameworkField = ""
|
||||||
|
self.logger.updateMetadata(previous: .topLevel, latest: self.baggage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func forEachBaggageItem(_ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
|
||||||
|
return try self.baggage.forEach(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FakeEventLoop {}
|
||||||
|
|
||||||
|
private extension Baggage {
|
||||||
|
var testID: Int? {
|
||||||
|
get {
|
||||||
|
return self[TestIDKey.self]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self[TestIDKey.self] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondTestID: String? {
|
||||||
|
get {
|
||||||
|
return self[SecondTestIDKey.self]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self[SecondTestIDKey.self] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestIDKey: Baggage.Key {
|
||||||
|
typealias Value = Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SecondTestIDKey: Baggage.Key {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
static let nameOverride: String? = "secondIDExplicitlyNamed"
|
||||||
|
}
|
|
@ -11,7 +11,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
//
|
//
|
||||||
// BaggageContextCarrierTests+XCTest.swift
|
// FrameworkContextTests+XCTest.swift
|
||||||
//
|
//
|
||||||
import XCTest
|
import XCTest
|
||||||
///
|
///
|
||||||
|
@ -20,14 +20,13 @@ import XCTest
|
||||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||||
///
|
///
|
||||||
|
|
||||||
extension BaggageContextCarrierTests {
|
extension FrameworkBaggageContextTests {
|
||||||
|
|
||||||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
@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)] {
|
static var allTests : [(String, (FrameworkBaggageContextTests) -> () throws -> Void)] {
|
||||||
return [
|
return [
|
||||||
("testBaggageContextSubscript", testBaggageContextSubscript),
|
("testBaggageContextSubscript", testBaggageContextSubscript),
|
||||||
("testBaggageContextForEach", testBaggageContextForEach),
|
("testBaggageContextForEach", testBaggageContextForEach),
|
||||||
("testBaggageContextCarriesItself", testBaggageContextCarriesItself),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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 BaggageContext
|
||||||
|
import Logging
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class FrameworkBaggageContextTests: XCTestCase {
|
||||||
|
func testBaggageContextSubscript() {
|
||||||
|
var context = TestFrameworkContext()
|
||||||
|
|
||||||
|
// mutate baggage context directly
|
||||||
|
context.baggage[OtherKey.self] = "test"
|
||||||
|
XCTAssertEqual(context.baggage.otherKey, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBaggageContextForEach() {
|
||||||
|
var contents = [AnyBaggageKey: Any]()
|
||||||
|
var context = TestFrameworkContext()
|
||||||
|
|
||||||
|
context.baggage.testKey = 42
|
||||||
|
context.baggage.otherKey = "test"
|
||||||
|
|
||||||
|
context.baggage.forEach { key, value in
|
||||||
|
contents[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertNotNil(contents[AnyBaggageKey(TestKey.self)])
|
||||||
|
XCTAssertEqual(contents[AnyBaggageKey(TestKey.self)] as? Int, 42)
|
||||||
|
XCTAssertNotNil(contents[AnyBaggageKey(OtherKey.self)])
|
||||||
|
XCTAssertEqual(contents[AnyBaggageKey(OtherKey.self)] as? String, "test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TestFrameworkContext: Context {
|
||||||
|
var baggage = Baggage.topLevel
|
||||||
|
|
||||||
|
private var _logger = Logger(label: "test")
|
||||||
|
var logger: Logger {
|
||||||
|
get {
|
||||||
|
return self._logger.with(self.baggage)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self._logger = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestKey: Baggage.Key {
|
||||||
|
typealias Value = Int
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
var testKey: Int? {
|
||||||
|
get {
|
||||||
|
return self[TestKey.self]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self[TestKey.self] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum OtherKey: Baggage.Key {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Baggage {
|
||||||
|
var otherKey: String? {
|
||||||
|
get {
|
||||||
|
return self[OtherKey.self]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self[OtherKey.self] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,11 +84,9 @@ internal struct TestLogHandler: LogHandler {
|
||||||
|
|
||||||
public var metadata: Logger.Metadata {
|
public var metadata: Logger.Metadata {
|
||||||
get {
|
get {
|
||||||
// return self.logger.metadata
|
|
||||||
return self.metadataLock.withLock { self._metadata }
|
return self.metadataLock.withLock { self._metadata }
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
// self.logger.metadata = newValue
|
|
||||||
self.metadataLock.withLock { self._metadata = newValue }
|
self.metadataLock.withLock { self._metadata = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,11 +94,9 @@ internal struct TestLogHandler: LogHandler {
|
||||||
// TODO: would be nice to delegate to local copy of logger but StdoutLogger is a reference type. why?
|
// TODO: would be nice to delegate to local copy of logger but StdoutLogger is a reference type. why?
|
||||||
subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
|
subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
|
||||||
get {
|
get {
|
||||||
// return self.logger[metadataKey: metadataKey]
|
|
||||||
return self.metadataLock.withLock { self._metadata[metadataKey] }
|
return self.metadataLock.withLock { self._metadata[metadataKey] }
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
// return logger[metadataKey: metadataKey] = newValue
|
|
||||||
self.metadataLock.withLock {
|
self.metadataLock.withLock {
|
||||||
self._metadata[metadataKey] = newValue
|
self._metadata[metadataKey] = newValue
|
||||||
}
|
}
|
|
@ -1,145 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
import Baggage
|
|
||||||
import BaggageLogging
|
|
||||||
import Logging
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
final class LoggingBaggageContextCarrierTests: XCTestCase {
|
|
||||||
func test_ContextWithLogger_dumpBaggage() throws {
|
|
||||||
let baggage = BaggageContext.background
|
|
||||||
let logger = Logger(label: "TheLogger")
|
|
||||||
|
|
||||||
var context: LoggingBaggageContextCarrier = ExampleFrameworkContext(context: baggage, logger: logger)
|
|
||||||
context.testID = 42
|
|
||||||
|
|
||||||
func frameworkFunctionDumpsBaggage(param: String, context: LoggingBaggageContextCarrier) -> String {
|
|
||||||
var s = ""
|
|
||||||
context.baggage.forEach { key, item in
|
|
||||||
s += "\(key.name): \(item)\n"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = frameworkFunctionDumpsBaggage(param: "x", context: context)
|
|
||||||
XCTAssertEqual(
|
|
||||||
result,
|
|
||||||
"""
|
|
||||||
TestIDKey: 42
|
|
||||||
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_ContextWithLogger_log_withBaggage() throws {
|
|
||||||
let baggage = BaggageContext.background
|
|
||||||
let logging = TestLogging()
|
|
||||||
let logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
|
|
||||||
|
|
||||||
var context: LoggingBaggageContextCarrier = ExampleFrameworkContext(context: baggage, logger: logger)
|
|
||||||
|
|
||||||
context.secondTestID = "value"
|
|
||||||
context.testID = 42
|
|
||||||
context.logger.info("Hello")
|
|
||||||
|
|
||||||
context.testID = nil
|
|
||||||
context.logger.warning("World")
|
|
||||||
|
|
||||||
logging.history.assertExist(level: .info, message: "Hello", metadata: [
|
|
||||||
"TestIDKey": .stringConvertible(42),
|
|
||||||
"secondIDExplicitlyNamed": "value",
|
|
||||||
])
|
|
||||||
logging.history.assertExist(level: .warning, message: "World", metadata: [
|
|
||||||
"secondIDExplicitlyNamed": "value",
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata() {
|
|
||||||
let baggage = BaggageContext.background
|
|
||||||
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",
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ExampleFrameworkContext: LoggingBaggageContextCarrier {
|
|
||||||
var baggage: BaggageContext
|
|
||||||
|
|
||||||
private var _logger: Logger
|
|
||||||
var logger: Logger {
|
|
||||||
return self._logger.with(context: self.baggage)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(context baggage: BaggageContext, logger: Logger) {
|
|
||||||
self.baggage = baggage
|
|
||||||
self._logger = logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CoolFrameworkContext: LoggingBaggageContextCarrier {
|
|
||||||
private var _logger: Logger = Logger(label: "some frameworks logger")
|
|
||||||
var logger: Logger {
|
|
||||||
return self._logger.with(context: self.baggage)
|
|
||||||
}
|
|
||||||
|
|
||||||
var baggage: BaggageContext = .background
|
|
||||||
|
|
||||||
// framework context defines other values as well
|
|
||||||
let frameworkField: String = ""
|
|
||||||
|
|
||||||
// including the popular eventLoop
|
|
||||||
let eventLoop: FakeEventLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FakeEventLoop {}
|
|
||||||
|
|
||||||
private extension BaggageContextProtocol {
|
|
||||||
var testID: Int? {
|
|
||||||
get {
|
|
||||||
return self[TestIDKey.self]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self[TestIDKey.self] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var secondTestID: String? {
|
|
||||||
get {
|
|
||||||
return self[SecondTestIDKey.self]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self[SecondTestIDKey.self] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum TestIDKey: BaggageContextKey {
|
|
||||||
typealias Value = Int
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum SecondTestIDKey: BaggageContextKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
static let name: String? = "secondIDExplicitlyNamed"
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
//
|
//
|
||||||
// BaggageContextTests+XCTest.swift
|
// BaggageTests+XCTest.swift
|
||||||
//
|
//
|
||||||
import XCTest
|
import XCTest
|
||||||
///
|
///
|
||||||
|
@ -20,10 +20,10 @@ import XCTest
|
||||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||||
///
|
///
|
||||||
|
|
||||||
extension BaggageContextTests {
|
extension BaggageTests {
|
||||||
|
|
||||||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
||||||
static var allTests : [(String, (BaggageContextTests) -> () throws -> Void)] {
|
static var allTests : [(String, (BaggageTests) -> () throws -> Void)] {
|
||||||
return [
|
return [
|
||||||
("testSubscriptAccess", testSubscriptAccess),
|
("testSubscriptAccess", testSubscriptAccess),
|
||||||
("testRecommendedConvenienceExtension", testRecommendedConvenienceExtension),
|
("testRecommendedConvenienceExtension", testRecommendedConvenienceExtension),
|
||||||
|
@ -31,7 +31,7 @@ extension BaggageContextTests {
|
||||||
("testSingleKeyBaggageDescription", testSingleKeyBaggageDescription),
|
("testSingleKeyBaggageDescription", testSingleKeyBaggageDescription),
|
||||||
("testMultiKeysBaggageDescription", testMultiKeysBaggageDescription),
|
("testMultiKeysBaggageDescription", testMultiKeysBaggageDescription),
|
||||||
("test_todo_context", test_todo_context),
|
("test_todo_context", test_todo_context),
|
||||||
("test_todo_empty", test_todo_empty),
|
("test_topLevel", test_topLevel),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,11 +14,11 @@
|
||||||
import Baggage
|
import Baggage
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class BaggageContextTests: XCTestCase {
|
final class BaggageTests: XCTestCase {
|
||||||
func testSubscriptAccess() {
|
func testSubscriptAccess() {
|
||||||
let testID = 42
|
let testID = 42
|
||||||
|
|
||||||
var baggage = BaggageContext.background
|
var baggage = Baggage.topLevel
|
||||||
XCTAssertNil(baggage[TestIDKey.self])
|
XCTAssertNil(baggage[TestIDKey.self])
|
||||||
|
|
||||||
baggage[TestIDKey.self] = testID
|
baggage[TestIDKey.self] = testID
|
||||||
|
@ -31,7 +31,7 @@ final class BaggageContextTests: XCTestCase {
|
||||||
func testRecommendedConvenienceExtension() {
|
func testRecommendedConvenienceExtension() {
|
||||||
let testID = 42
|
let testID = 42
|
||||||
|
|
||||||
var baggage = BaggageContext.background
|
var baggage = Baggage.topLevel
|
||||||
XCTAssertNil(baggage.testID)
|
XCTAssertNil(baggage.testID)
|
||||||
|
|
||||||
baggage.testID = testID
|
baggage.testID = testID
|
||||||
|
@ -42,26 +42,26 @@ final class BaggageContextTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEmptyBaggageDescription() {
|
func testEmptyBaggageDescription() {
|
||||||
XCTAssertEqual(String(describing: BaggageContext.background), "BaggageContext(keys: [])")
|
XCTAssertEqual(String(describing: Baggage.topLevel), "Baggage(keys: [])")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSingleKeyBaggageDescription() {
|
func testSingleKeyBaggageDescription() {
|
||||||
var baggage = BaggageContext.background
|
var baggage = Baggage.topLevel
|
||||||
baggage.testID = 42
|
baggage.testID = 42
|
||||||
|
|
||||||
XCTAssertEqual(String(describing: baggage), #"BaggageContext(keys: ["TestIDKey"])"#)
|
XCTAssertEqual(String(describing: baggage), #"Baggage(keys: ["TestIDKey"])"#)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultiKeysBaggageDescription() {
|
func testMultiKeysBaggageDescription() {
|
||||||
var baggage = BaggageContext.background
|
var baggage = Baggage.topLevel
|
||||||
baggage.testID = 42
|
baggage.testID = 42
|
||||||
baggage[SecondTestIDKey.self] = "test"
|
baggage[SecondTestIDKey.self] = "test"
|
||||||
|
|
||||||
let description = String(describing: baggage)
|
let description = String(describing: baggage)
|
||||||
XCTAssert(description.starts(with: "BaggageContext(keys: ["))
|
XCTAssert(description.starts(with: "Baggage(keys: ["), "Was: \(description)")
|
||||||
// use contains instead of `XCTAssertEqual` because the order is non-predictable (Dictionary)
|
// use contains instead of `XCTAssertEqual` because the order is non-predictable (Dictionary)
|
||||||
XCTAssert(description.contains("TestIDKey"))
|
XCTAssert(description.contains("TestIDKey"), "Was: \(description)")
|
||||||
XCTAssert(description.contains("ExplicitKeyName"))
|
XCTAssert(description.contains("ExplicitKeyName"), "Was: \(description)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== ------------------------------------------------------------------------------------------------------------
|
// ==== ------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -69,45 +69,33 @@ final class BaggageContextTests: XCTestCase {
|
||||||
|
|
||||||
func test_todo_context() {
|
func test_todo_context() {
|
||||||
// the to-do context can be used to record intentions for why a context could not be passed through
|
// the to-do context can be used to record intentions for why a context could not be passed through
|
||||||
let context = BaggageContext.TODO("#1245 Some other library should be adjusted to pass us context")
|
let context = Baggage.TODO("#1245 Some other library should be adjusted to pass us context")
|
||||||
_ = context // avoid "not used" warning
|
_ = context // avoid "not used" warning
|
||||||
|
|
||||||
// TODO: Can't work with protocols; re-consider the entire carrier approach... Context being a Baggage + Logger, and a specific type.
|
|
||||||
// func take(context: BaggageContextProtocol) {
|
|
||||||
// _ = context // ignore
|
|
||||||
// }
|
|
||||||
// take(context: .TODO("pass from request instead"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_todo_empty() {
|
func test_topLevel() {
|
||||||
let context = BaggageContext.background
|
let context = Baggage.topLevel
|
||||||
_ = context // avoid "not used" warning
|
_ = context // avoid "not used" warning
|
||||||
|
|
||||||
// TODO: Can't work with protocols; re-consider the entire carrier approach... Context being a Baggage + Logger, and a specific type.
|
|
||||||
// static member 'empty' cannot be used on protocol metatype 'BaggageContextProtocol.Protocol'
|
|
||||||
// func take(context: BaggageContextProtocol) {
|
|
||||||
// _ = context // ignore
|
|
||||||
// }
|
|
||||||
// take(context: .background)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestIDKey: BaggageContextKey {
|
private enum TestIDKey: Baggage.Key {
|
||||||
typealias Value = Int
|
typealias Value = Int
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BaggageContext {
|
private extension Baggage {
|
||||||
var testID: Int? {
|
var testID: Int? {
|
||||||
get {
|
get {
|
||||||
return self[TestIDKey.self]
|
return self[TestIDKey.self]
|
||||||
} set {
|
}
|
||||||
|
set {
|
||||||
self[TestIDKey.self] = newValue
|
self[TestIDKey.self] = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SecondTestIDKey: BaggageContextKey {
|
private enum SecondTestIDKey: Baggage.Key {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
|
|
||||||
static let name: String? = "ExplicitKeyName"
|
static let nameOverride: String? = "ExplicitKeyName"
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ import XCTest
|
||||||
///
|
///
|
||||||
|
|
||||||
#if os(Linux) || os(FreeBSD)
|
#if os(Linux) || os(FreeBSD)
|
||||||
@testable import BaggageLoggingTests
|
@testable import BaggageContextTests
|
||||||
@testable import BaggageTests
|
@testable import BaggageTests
|
||||||
|
|
||||||
// This protocol is necessary to we can call the 'run' method (on an existential of this protocol)
|
// This protocol is necessary to we can call the 'run' method (on an existential of this protocol)
|
||||||
|
@ -33,9 +33,9 @@ class LinuxMainRunnerImpl: LinuxMainRunner {
|
||||||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
|
||||||
func run() {
|
func run() {
|
||||||
XCTMain([
|
XCTMain([
|
||||||
testCase(BaggageContextCarrierTests.allTests),
|
|
||||||
testCase(BaggageContextTests.allTests),
|
testCase(BaggageContextTests.allTests),
|
||||||
testCase(LoggingBaggageContextCarrierTests.allTests),
|
testCase(BaggageTests.allTests),
|
||||||
|
testCase(FrameworkBaggageContextTests.allTests),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue