Skip to content
This repository was archived by the owner on Apr 23, 2021. It is now read-only.

Commit caa867b

Browse files
authored
Merge pull request #9 from ktoso/wip-holders
WIP #7: on holding baggage in framework protocols
2 parents c78c490 + fa304c6 commit caa867b

File tree

9 files changed

+779
-8
lines changed

9 files changed

+779
-8
lines changed

Package.resolved

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,34 @@ import PackageDescription
44
let package = Package(
55
name: "swift-baggage-context",
66
products: [
7-
.library(name: "Baggage", targets: ["Baggage"])
7+
.library(name: "Baggage",
8+
targets: [
9+
"Baggage"
10+
]
11+
),
12+
.library(name: "BaggageLogging",
13+
targets: [
14+
"BaggageLogging"
15+
]
16+
),
17+
],
18+
dependencies: [
19+
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0")
820
],
921
targets: [
22+
1023
.target(
1124
name: "Baggage",
1225
dependencies: []
1326
),
1427

28+
.target(
29+
name: "BaggageLogging",
30+
dependencies: [
31+
.product(name: "Logging", package: "swift-log")
32+
]
33+
),
34+
1535
// ==== --------------------------------------------------------------------------------------------------------
1636
// MARK: Tests
1737

@@ -22,15 +42,24 @@ let package = Package(
2242
]
2343
),
2444

45+
.testTarget(
46+
name: "BaggageLoggingTests",
47+
dependencies: [
48+
"Baggage",
49+
"BaggageLogging"
50+
]
51+
),
52+
2553
// ==== --------------------------------------------------------------------------------------------------------
2654
// MARK: Performance / Benchmarks
2755

2856
.target(
2957
name: "Benchmarks",
3058
dependencies: [
3159
"Baggage",
60+
"BaggageLogging",
3261
"SwiftBenchmarkTools",
33-
]
62+
]
3463
),
3564
.target(
3665
name: "SwiftBenchmarkTools",

Sources/Baggage/BaggageContext.swift

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
/// Libraries may also want to provide an extension, offering the values that users are expected to reach for
3636
/// using the following pattern:
3737
///
38-
/// extension BaggageContext {
38+
/// extension BaggageContextProtocol {
3939
/// var testID: TestIDKey.Value {
4040
/// get {
4141
/// self[TestIDKey.self]
@@ -44,7 +44,7 @@
4444
/// }
4545
/// }
4646
/// }
47-
public struct BaggageContext {
47+
public struct BaggageContext: BaggageContextProtocol {
4848
private var _storage = [AnyBaggageContextKey: ValueContainer]()
4949

5050
/// Create an empty `BaggageContext`.
@@ -60,10 +60,9 @@ public struct BaggageContext {
6060
}
6161
}
6262

63-
public var baggageItems: [AnyBaggageContextKey: Any] {
64-
// TODO: key may not be unique
65-
self._storage.reduce(into: [:]) {
66-
$0[$1.key] = $1.value.value
63+
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
64+
self._storage.forEach { key, container in
65+
callback(key, container.value)
6766
}
6867
}
6968

@@ -82,6 +81,32 @@ extension BaggageContext: CustomStringConvertible {
8281
}
8382
}
8483

84+
public protocol BaggageContextProtocol {
85+
/// Provides type-safe access to the baggage's values.
86+
///
87+
/// Rather than using this subscript directly, users are encouraged to offer a convenience accessor to their values,
88+
/// using the following pattern:
89+
///
90+
/// extension BaggageContextProtocol {
91+
/// var testID: TestIDKey.Value {
92+
/// get {
93+
/// self[TestIDKey.self]
94+
/// } set {
95+
/// self[TestIDKey.self] = newValue
96+
/// }
97+
/// }
98+
/// }
99+
subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? { get set }
100+
101+
/// Iterates over the baggage context's contents invoking the callback one-by one.
102+
///
103+
/// - Parameter callback: invoked with the type erased key and value stored for the key in this baggage.
104+
func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void)
105+
}
106+
107+
// ==== ------------------------------------------------------------------------
108+
// MARK: Baggage keys
109+
85110
/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` gurantees type-safety.
86111
/// To give your `BaggageContextKey` an explicit name you may override the `name` property.
87112
public protocol BaggageContextKey {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Baggage Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
// ==== ----------------------------------------------------------------------------------------------------------------
15+
// MARK: Framework Context Protocols
16+
17+
/// Framework context protocols may conform to this protocol if they are used to carry a baggage object.
18+
///
19+
/// Notice that the baggage context property is spelled as `baggage`, this is purposefully designed in order to read well
20+
/// with framework context's which often will be passed as `context: FrameworkContext` and used as `context.baggage`.
21+
///
22+
/// Such carrier protocol also conforms to `BaggageContextProtocol` meaning that it has the same convenient accessors
23+
/// as the actual baggage type. Users should be able to use the `context.myValue` the same way if a raw baggage context,
24+
/// or a framework context was passed around as `context` parameter, allowing for easier migrations between those two when needed.
25+
public protocol BaggageContextCarrier: BaggageContextProtocol {
26+
/// The underlying `BaggageContext`.
27+
var baggage: BaggageContext { get set }
28+
}
29+
30+
extension BaggageContextCarrier {
31+
public subscript<Key: BaggageContextKey>(baggageKey: Key.Type) -> Key.Value? {
32+
get {
33+
self.baggage[baggageKey]
34+
} set {
35+
self.baggage[baggageKey] = newValue
36+
}
37+
}
38+
39+
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
40+
self.baggage.forEach(callback)
41+
}
42+
}
43+
44+
/// A baggage itself also is a carrier of _itself_.
45+
extension BaggageContext: BaggageContextCarrier {
46+
public var baggage: BaggageContext {
47+
get {
48+
self
49+
}
50+
set {
51+
self = newValue
52+
}
53+
}
54+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Baggage Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Baggage
15+
import Logging
16+
17+
// ==== ----------------------------------------------------------------------------------------------------------------
18+
// MARK: BaggageContext (as additional Logger.Metadata) LogHandler
19+
20+
/// Proxying log handler which adds `BaggageContext` as metadata when log events are to be emitted.
21+
public struct BaggageMetadataLogHandler: LogHandler {
22+
var underlying: Logger
23+
let context: BaggageContext
24+
25+
public init(logger underlying: Logger, context: BaggageContext) {
26+
self.underlying = underlying
27+
self.context = context
28+
}
29+
30+
public var logLevel: Logger.Level {
31+
get {
32+
self.underlying.logLevel
33+
}
34+
set {
35+
self.underlying.logLevel = newValue
36+
}
37+
}
38+
39+
public func log(
40+
level: Logger.Level,
41+
message: Logger.Message,
42+
metadata: Logger.Metadata?,
43+
source: String,
44+
file: String,
45+
function: String,
46+
line: UInt
47+
) {
48+
guard self.underlying.logLevel <= level else {
49+
return
50+
}
51+
52+
var effectiveMetadata = self.baggageAsMetadata()
53+
if let metadata = metadata {
54+
effectiveMetadata.merge(metadata, uniquingKeysWith: { _, r in r })
55+
}
56+
self.underlying.log(level: level, message, metadata: effectiveMetadata, source: source, file: file, function: function, line: line)
57+
}
58+
59+
public var metadata: Logger.Metadata {
60+
get {
61+
[:]
62+
}
63+
set {
64+
newValue.forEach { k, v in
65+
self.underlying[metadataKey: k] = v
66+
}
67+
}
68+
}
69+
70+
/// Note that this does NOT look up inside the baggage.
71+
///
72+
/// This is because a context lookup either has to use the specific type key, or iterate over all keys to locate one by name,
73+
/// which may be incorrect still, thus rather than making an potentially slightly incorrect lookup, we do not implement peeking
74+
/// into a baggage with String keys through this handler (as that is not a capability `BaggageContext` offers in any case.
75+
public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
76+
get {
77+
self.underlying[metadataKey: metadataKey]
78+
}
79+
set {
80+
self.underlying[metadataKey: metadataKey] = newValue
81+
}
82+
}
83+
84+
private func baggageAsMetadata() -> Logger.Metadata {
85+
var effectiveMetadata: Logger.Metadata = [:]
86+
self.context.forEach { key, value in
87+
if let convertible = value as? String {
88+
effectiveMetadata[key.name] = .string(convertible)
89+
} else if let convertible = value as? CustomStringConvertible {
90+
effectiveMetadata[key.name] = .stringConvertible(convertible)
91+
} else {
92+
effectiveMetadata[key.name] = .stringConvertible(BaggageValueCustomStringConvertible(value))
93+
}
94+
}
95+
96+
return effectiveMetadata
97+
}
98+
99+
struct BaggageValueCustomStringConvertible: CustomStringConvertible {
100+
let value: Any
101+
102+
init(_ value: Any) {
103+
self.value = value
104+
}
105+
106+
var description: String {
107+
"\(self.value)"
108+
}
109+
}
110+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Baggage Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Baggage
15+
import Logging
16+
17+
extension Logger {
18+
/// Returns a logger that in addition to any explicit metadata passed to log statements,
19+
/// also includes the `BaggageContext` adapted into metadata values.
20+
///
21+
/// The rendering of baggage values into metadata values is performed on demand,
22+
/// whenever a log statement is effective (i.e. will be logged, according to active `logLevel`).
23+
public func with(context: BaggageContext) -> Logger {
24+
Logger(
25+
label: self.label,
26+
factory: { _ in BaggageMetadataLogHandler(logger: self, context: context) }
27+
)
28+
}
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Baggage Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Baggage
15+
import Logging
16+
17+
/// A `BaggageContextLogging` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
18+
/// and to such frameworks to pass their context as `BaggageContextCarrier`.
19+
public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
20+
/// The logger associated with this carrier context.
21+
///
22+
/// It should automatically populate the loggers metadata based on the `BaggageContext` associated with this context object.
23+
///
24+
/// ### Implementation note
25+
///
26+
/// Libraries and/or frameworks which conform to this protocol with their "Framework Context" types,
27+
/// SHOULD implement this logger by wrapping the "raw" logger associated with this context with the `logger.with(BaggageContext:)` function,
28+
/// which efficiently handles the bridging of baggage to logging metadata values.
29+
///
30+
/// Writes to the `logger` metadata SHOULD NOT be reflected in the `baggage`,
31+
/// however writes to the underlying `baggage` SHOULD be reflected in the `logger`.
32+
var logger: Logger { get set }
33+
}

0 commit comments

Comments
 (0)