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
|
||||
|
||||
let package = Package(
|
||||
name: "swift-baggage-context",
|
||||
name: "swift-context",
|
||||
products: [
|
||||
.library(
|
||||
name: "Baggage",
|
||||
|
@ -11,14 +11,14 @@ let package = Package(
|
|||
]
|
||||
),
|
||||
.library(
|
||||
name: "BaggageLogging",
|
||||
name: "BaggageContext",
|
||||
targets: [
|
||||
"BaggageLogging",
|
||||
"BaggageContext",
|
||||
]
|
||||
),
|
||||
],
|
||||
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: [
|
||||
.target(
|
||||
|
@ -27,7 +27,7 @@ let package = Package(
|
|||
),
|
||||
|
||||
.target(
|
||||
name: "BaggageLogging",
|
||||
name: "BaggageContext",
|
||||
dependencies: [
|
||||
"Baggage",
|
||||
.product(name: "Logging", package: "swift-log"),
|
||||
|
@ -45,10 +45,10 @@ let package = Package(
|
|||
),
|
||||
|
||||
.testTarget(
|
||||
name: "BaggageLoggingTests",
|
||||
name: "BaggageContextTests",
|
||||
dependencies: [
|
||||
"Baggage",
|
||||
"BaggageLogging",
|
||||
"BaggageContext",
|
||||
]
|
||||
),
|
||||
|
||||
|
@ -56,15 +56,14 @@ let package = Package(
|
|||
// MARK: Performance / Benchmarks
|
||||
|
||||
.target(
|
||||
name: "BaggageBenchmarks",
|
||||
name: "BaggageContextBenchmarks",
|
||||
dependencies: [
|
||||
"Baggage",
|
||||
"BaggageLogging",
|
||||
"BaggageBenchmarkTools",
|
||||
"BaggageContext",
|
||||
"BaggageContextBenchmarkTools",
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "BaggageBenchmarkTools",
|
||||
name: "BaggageContextBenchmarkTools",
|
||||
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
|
||||
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 BaggageBenchmarkTools
|
||||
import BaggageContextBenchmarkTools
|
||||
import Dispatch
|
||||
import class Foundation.NSLock
|
||||
|
||||
public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
||||
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||
// MARK: "Read only" context passing around
|
||||
BenchmarkInfo(
|
||||
name: "BaggagePassingBenchmarks.pass_async_empty_100_000 ",
|
||||
runFunction: { _ in
|
||||
let context = BaggageContext.background
|
||||
let context = Baggage.topLevel
|
||||
pass_async(context: context, times: 100_000)
|
||||
},
|
||||
tags: [],
|
||||
|
@ -31,7 +32,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
|||
BenchmarkInfo(
|
||||
name: "BaggagePassingBenchmarks.pass_async_smol_100_000 ",
|
||||
runFunction: { _ in
|
||||
var context = BaggageContext.background
|
||||
var context = Baggage.topLevel
|
||||
context.k1 = "one"
|
||||
context.k2 = "two"
|
||||
context.k3 = "three"
|
||||
|
@ -45,7 +46,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
|||
BenchmarkInfo(
|
||||
name: "BaggagePassingBenchmarks.pass_async_small_nonconst_100_000",
|
||||
runFunction: { _ in
|
||||
var context = BaggageContext.background
|
||||
var context = Baggage.topLevel
|
||||
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
||||
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
||||
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
||||
|
@ -65,7 +66,7 @@ public let BaggagePassingBenchmarks: [BenchmarkInfo] = [
|
|||
BenchmarkInfo(
|
||||
name: "BaggagePassingBenchmarks.pass_mut_async_small_100_000 ",
|
||||
runFunction: { _ in
|
||||
var context = BaggageContext.background
|
||||
var context = Baggage.topLevel
|
||||
context.k1 = "\(Int.random(in: 1 ... Int.max))"
|
||||
context.k2 = "\(Int.random(in: 1 ... Int.max))"
|
||||
context.k3 = "\(Int.random(in: 1 ... Int.max))"
|
||||
|
@ -87,10 +88,10 @@ private func tearDown() {
|
|||
}
|
||||
|
||||
@inline(never)
|
||||
func pass_async(context: BaggageContext, times remaining: Int) {
|
||||
func pass_async(context: Baggage, times remaining: Int) {
|
||||
let latch = CountDownLatch(from: 1)
|
||||
|
||||
func pass_async0(context: BaggageContext, times remaining: Int) {
|
||||
func pass_async0(context: Baggage, times remaining: Int) {
|
||||
if remaining == 0 {
|
||||
latch.countDown()
|
||||
}
|
||||
|
@ -105,11 +106,11 @@ func pass_async(context: BaggageContext, times remaining: Int) {
|
|||
}
|
||||
|
||||
@inline(never)
|
||||
func pass_mut_async(context: BaggageContext, times remaining: Int) {
|
||||
func pass_mut_async(context: Baggage, times remaining: Int) {
|
||||
var context = context
|
||||
let latch = CountDownLatch(from: 1)
|
||||
|
||||
func pass_async0(context: BaggageContext, times remaining: Int) {
|
||||
func pass_async0(context: Baggage, times remaining: Int) {
|
||||
if remaining == 0 {
|
||||
latch.countDown()
|
||||
}
|
||||
|
@ -132,31 +133,31 @@ func pass_mut_async(context: BaggageContext, times remaining: Int) {
|
|||
// ==== ----------------------------------------------------------------------------------------------------------------
|
||||
// MARK: Baggage Keys
|
||||
|
||||
private enum TestPassCounterKey: BaggageContextKey {
|
||||
private enum TestPassCounterKey: BaggageKey {
|
||||
typealias Value = Int
|
||||
}
|
||||
|
||||
private enum TestK1: BaggageContextKey {
|
||||
private enum TestK1: BaggageKey {
|
||||
typealias Value = String
|
||||
}
|
||||
|
||||
private enum TestK2: BaggageContextKey {
|
||||
private enum TestK2: BaggageKey {
|
||||
typealias Value = String
|
||||
}
|
||||
|
||||
private enum TestK3: BaggageContextKey {
|
||||
private enum TestK3: BaggageKey {
|
||||
typealias Value = String
|
||||
}
|
||||
|
||||
private enum TestK4: BaggageContextKey {
|
||||
private enum TestK4: BaggageKey {
|
||||
typealias Value = String
|
||||
}
|
||||
|
||||
private enum TestKD1: BaggageContextKey {
|
||||
private enum TestKD1: BaggageKey {
|
||||
typealias Value = [String: String]
|
||||
}
|
||||
|
||||
extension BaggageContext {
|
||||
extension Baggage {
|
||||
fileprivate var passCounter: TestPassCounterKey.Value {
|
||||
get { return self[TestPassCounterKey.self] ?? 0 }
|
||||
set { self[TestPassCounterKey.self] = newValue }
|
|
@ -11,7 +11,7 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import BaggageBenchmarkTools
|
||||
import BaggageContextBenchmarkTools
|
||||
|
||||
assert({
|
||||
print("===========================================================================")
|
||||
|
@ -37,5 +37,6 @@ private func registerBenchmark(_ name: String, _ function: @escaping (Int) -> Vo
|
|||
}
|
||||
|
||||
registerBenchmark(BaggagePassingBenchmarks)
|
||||
registerBenchmark(BaggageLoggingBenchmarks)
|
||||
|
||||
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
|
||||
///
|
||||
|
@ -20,14 +20,15 @@ import XCTest
|
|||
/// 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")
|
||||
static var allTests : [(String, (LoggingBaggageContextCarrierTests) -> () throws -> Void)] {
|
||||
static var allTests : [(String, (BaggageContextTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("test_ContextWithLogger_dumpBaggage", test_ContextWithLogger_dumpBaggage),
|
||||
("test_ContextWithLogger_log_withBaggage", test_ContextWithLogger_log_withBaggage),
|
||||
("test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata", test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata),
|
||||
("test_ExampleFrameworkContext_dumpBaggage", test_ExampleFrameworkContext_dumpBaggage),
|
||||
("test_ExampleFrameworkContext_log_withBaggage", test_ExampleFrameworkContext_log_withBaggage),
|
||||
("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
|
||||
///
|
||||
|
@ -20,14 +20,13 @@ import XCTest
|
|||
/// 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")
|
||||
static var allTests : [(String, (BaggageContextCarrierTests) -> () throws -> Void)] {
|
||||
static var allTests : [(String, (FrameworkBaggageContextTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("testBaggageContextSubscript", testBaggageContextSubscript),
|
||||
("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 {
|
||||
get {
|
||||
// return self.logger.metadata
|
||||
return self.metadataLock.withLock { self._metadata }
|
||||
}
|
||||
set {
|
||||
// self.logger.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?
|
||||
subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
|
||||
get {
|
||||
// return self.logger[metadataKey: metadataKey]
|
||||
return self.metadataLock.withLock { self._metadata[metadataKey] }
|
||||
}
|
||||
set {
|
||||
// return logger[metadataKey: metadataKey] = newValue
|
||||
self.metadataLock.withLock {
|
||||
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
|
||||
///
|
||||
|
@ -20,10 +20,10 @@ import XCTest
|
|||
/// 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")
|
||||
static var allTests : [(String, (BaggageContextTests) -> () throws -> Void)] {
|
||||
static var allTests : [(String, (BaggageTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("testSubscriptAccess", testSubscriptAccess),
|
||||
("testRecommendedConvenienceExtension", testRecommendedConvenienceExtension),
|
||||
|
@ -31,7 +31,7 @@ extension BaggageContextTests {
|
|||
("testSingleKeyBaggageDescription", testSingleKeyBaggageDescription),
|
||||
("testMultiKeysBaggageDescription", testMultiKeysBaggageDescription),
|
||||
("test_todo_context", test_todo_context),
|
||||
("test_todo_empty", test_todo_empty),
|
||||
("test_topLevel", test_topLevel),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -14,11 +14,11 @@
|
|||
import Baggage
|
||||
import XCTest
|
||||
|
||||
final class BaggageContextTests: XCTestCase {
|
||||
final class BaggageTests: XCTestCase {
|
||||
func testSubscriptAccess() {
|
||||
let testID = 42
|
||||
|
||||
var baggage = BaggageContext.background
|
||||
var baggage = Baggage.topLevel
|
||||
XCTAssertNil(baggage[TestIDKey.self])
|
||||
|
||||
baggage[TestIDKey.self] = testID
|
||||
|
@ -31,7 +31,7 @@ final class BaggageContextTests: XCTestCase {
|
|||
func testRecommendedConvenienceExtension() {
|
||||
let testID = 42
|
||||
|
||||
var baggage = BaggageContext.background
|
||||
var baggage = Baggage.topLevel
|
||||
XCTAssertNil(baggage.testID)
|
||||
|
||||
baggage.testID = testID
|
||||
|
@ -42,26 +42,26 @@ final class BaggageContextTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testEmptyBaggageDescription() {
|
||||
XCTAssertEqual(String(describing: BaggageContext.background), "BaggageContext(keys: [])")
|
||||
XCTAssertEqual(String(describing: Baggage.topLevel), "Baggage(keys: [])")
|
||||
}
|
||||
|
||||
func testSingleKeyBaggageDescription() {
|
||||
var baggage = BaggageContext.background
|
||||
var baggage = Baggage.topLevel
|
||||
baggage.testID = 42
|
||||
|
||||
XCTAssertEqual(String(describing: baggage), #"BaggageContext(keys: ["TestIDKey"])"#)
|
||||
XCTAssertEqual(String(describing: baggage), #"Baggage(keys: ["TestIDKey"])"#)
|
||||
}
|
||||
|
||||
func testMultiKeysBaggageDescription() {
|
||||
var baggage = BaggageContext.background
|
||||
var baggage = Baggage.topLevel
|
||||
baggage.testID = 42
|
||||
baggage[SecondTestIDKey.self] = "test"
|
||||
|
||||
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)
|
||||
XCTAssert(description.contains("TestIDKey"))
|
||||
XCTAssert(description.contains("ExplicitKeyName"))
|
||||
XCTAssert(description.contains("TestIDKey"), "Was: \(description)")
|
||||
XCTAssert(description.contains("ExplicitKeyName"), "Was: \(description)")
|
||||
}
|
||||
|
||||
// ==== ------------------------------------------------------------------------------------------------------------
|
||||
|
@ -69,45 +69,33 @@ final class BaggageContextTests: XCTestCase {
|
|||
|
||||
func test_todo_context() {
|
||||
// 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
|
||||
|
||||
// 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() {
|
||||
let context = BaggageContext.background
|
||||
func test_topLevel() {
|
||||
let context = Baggage.topLevel
|
||||
_ = 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
|
||||
}
|
||||
|
||||
private extension BaggageContext {
|
||||
private extension Baggage {
|
||||
var testID: Int? {
|
||||
get {
|
||||
return self[TestIDKey.self]
|
||||
} set {
|
||||
}
|
||||
set {
|
||||
self[TestIDKey.self] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SecondTestIDKey: BaggageContextKey {
|
||||
private enum SecondTestIDKey: Baggage.Key {
|
||||
typealias Value = String
|
||||
|
||||
static let name: String? = "ExplicitKeyName"
|
||||
static let nameOverride: String? = "ExplicitKeyName"
|
||||
}
|
|
@ -21,7 +21,7 @@ import XCTest
|
|||
///
|
||||
|
||||
#if os(Linux) || os(FreeBSD)
|
||||
@testable import BaggageLoggingTests
|
||||
@testable import BaggageContextTests
|
||||
@testable import BaggageTests
|
||||
|
||||
// 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")
|
||||
func run() {
|
||||
XCTMain([
|
||||
testCase(BaggageContextCarrierTests.allTests),
|
||||
testCase(BaggageContextTests.allTests),
|
||||
testCase(LoggingBaggageContextCarrierTests.allTests),
|
||||
testCase(BaggageTests.allTests),
|
||||
testCase(FrameworkBaggageContextTests.allTests),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue