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

Commit

Permalink
Merge pull request #9 from ktoso/wip-holders
Browse files Browse the repository at this point in the history
WIP #7: on holding baggage in framework protocols
  • Loading branch information
ktoso authored Aug 5, 2020
2 parents c78c490 + fa304c6 commit caa867b
Show file tree
Hide file tree
Showing 9 changed files with 779 additions and 8 deletions.
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "57c6bd04256ba47590ee2285e208f731210c5c10",
"version": "1.3.0"
}
}
]
},
"version": 1
}
33 changes: 31 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,34 @@ import PackageDescription
let package = Package(
name: "swift-baggage-context",
products: [
.library(name: "Baggage", targets: ["Baggage"])
.library(name: "Baggage",
targets: [
"Baggage"
]
),
.library(name: "BaggageLogging",
targets: [
"BaggageLogging"
]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0")
],
targets: [

.target(
name: "Baggage",
dependencies: []
),

.target(
name: "BaggageLogging",
dependencies: [
.product(name: "Logging", package: "swift-log")
]
),

// ==== --------------------------------------------------------------------------------------------------------
// MARK: Tests

Expand All @@ -22,15 +42,24 @@ let package = Package(
]
),

.testTarget(
name: "BaggageLoggingTests",
dependencies: [
"Baggage",
"BaggageLogging"
]
),

// ==== --------------------------------------------------------------------------------------------------------
// MARK: Performance / Benchmarks

.target(
name: "Benchmarks",
dependencies: [
"Baggage",
"BaggageLogging",
"SwiftBenchmarkTools",
]
]
),
.target(
name: "SwiftBenchmarkTools",
Expand Down
37 changes: 31 additions & 6 deletions Sources/Baggage/BaggageContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
/// Libraries may also want to provide an extension, offering the values that users are expected to reach for
/// using the following pattern:
///
/// extension BaggageContext {
/// extension BaggageContextProtocol {
/// var testID: TestIDKey.Value {
/// get {
/// self[TestIDKey.self]
Expand All @@ -44,7 +44,7 @@
/// }
/// }
/// }
public struct BaggageContext {
public struct BaggageContext: BaggageContextProtocol {
private var _storage = [AnyBaggageContextKey: ValueContainer]()

/// Create an empty `BaggageContext`.
Expand All @@ -60,10 +60,9 @@ public struct BaggageContext {
}
}

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

Expand All @@ -82,6 +81,32 @@ extension BaggageContext: CustomStringConvertible {
}
}

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 }

/// Iterates over the baggage context's contents invoking the callback one-by one.
///
/// - Parameter callback: invoked with the type erased key and value stored for the key in this baggage.
func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void)
}

// ==== ------------------------------------------------------------------------
// MARK: Baggage keys

/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` gurantees type-safety.
/// To give your `BaggageContextKey` an explicit name you may override the `name` property.
public protocol BaggageContextKey {
Expand Down
54 changes: 54 additions & 0 deletions Sources/Baggage/BaggageContextCarrier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===----------------------------------------------------------------------===//
//
// 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 {
self.baggage[baggageKey]
} set {
self.baggage[baggageKey] = newValue
}
}

public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
self.baggage.forEach(callback)
}
}

/// A baggage itself also is a carrier of _itself_.
extension BaggageContext: BaggageContextCarrier {
public var baggage: BaggageContext {
get {
self
}
set {
self = newValue
}
}
}
110 changes: 110 additions & 0 deletions Sources/BaggageLogging/BaggageMetadataLogHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//===----------------------------------------------------------------------===//
//
// 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.
public struct BaggageMetadataLogHandler: LogHandler {
var underlying: Logger
let context: BaggageContext

public init(logger underlying: Logger, context: BaggageContext) {
self.underlying = underlying
self.context = context
}

public var logLevel: Logger.Level {
get {
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 {
[:]
}
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 {
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 {
"\(self.value)"
}
}
}
29 changes: 29 additions & 0 deletions Sources/BaggageLogging/Logger+BaggageContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// 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 {
Logger(
label: self.label,
factory: { _ in BaggageMetadataLogHandler(logger: self, context: context) }
)
}
}
33 changes: 33 additions & 0 deletions Sources/BaggageLogging/LoggingBaggageContextCarrier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// 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 `BaggageContextLogging` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
/// and to such frameworks to pass their context as `BaggageContextCarrier`.
public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
/// The logger associated with this carrier context.
///
/// 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.
///
/// Writes to the `logger` metadata SHOULD NOT be reflected in the `baggage`,
/// however writes to the underlying `baggage` SHOULD be reflected in the `logger`.
var logger: Logger { get set }
}
Loading

0 comments on commit caa867b

Please sign in to comment.