Skip to content

Commit

Permalink
Add logging metadata provider (#38)
Browse files Browse the repository at this point in the history
* Add logging metadata provider

* [CI] Compile example using Swift 5.7

* [CI] Run unit tests with Swift 5.7
  • Loading branch information
slashmo committed Jan 22, 2023
1 parent 383b5b4 commit 66031a2
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 5 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yaml
Expand Up @@ -13,6 +13,7 @@ jobs:
- swift:5.4
- swift:5.5
- swift:5.6
- swift:5.7
- swiftlang/swift:nightly-master
container: ${{ matrix.images }}
steps:
Expand All @@ -38,9 +39,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Swift
uses: slashmo/install-swift@v0.3.0
uses: slashmo/install-swift@v0.4.0
with:
version: 5.6
version: 5.7
- name: Resolve Swift dependencies
run: swift package resolve
- name: Build
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -10,3 +10,4 @@ xcuserdata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
.vscode/
2 changes: 1 addition & 1 deletion Package.swift
Expand Up @@ -15,7 +15,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-distributed-tracing.git", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0"),
],
Expand Down
39 changes: 39 additions & 0 deletions Sources/OpenTelemetry/Logging/MetadataProvider+OTel.swift
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenTelemetry open source project
//
// Copyright (c) 2023 Moritz Lang and the Swift OpenTelemetry project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import InstrumentationBaggage
import Logging

#if swift(>=5.5) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Logger.MetadataProvider {
/// A metadata provider exposing the current trace and span ID.
///
/// - Parameters:
/// - traceIDKey: The metadata key of the trace ID. Defaults to "trace-id".
/// - spanIDKey: The metadata key of the span ID. Defaults to "span-id".
/// - Returns: A metadata provider ready to use with Logging.
public static func otel(traceIDKey: String = "trace-id", spanIDKey: String = "span-id") -> Logger.MetadataProvider {
.init {
guard let spanContext = Baggage.current?.spanContext else { return [:] }
return [
traceIDKey: "\(spanContext.traceID)",
spanIDKey: "\(spanContext.spanID)",
]
}
}

/// A metadata provider exposing the current trace and span ID.
public static let otel = Logger.MetadataProvider.otel()
}
#endif
123 changes: 123 additions & 0 deletions Tests/OpenTelemetryTests/Logging/MetadataProviderTests.swift
@@ -0,0 +1,123 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenTelemetry open source project
//
// Copyright (c) 2023 Moritz Lang and the Swift OpenTelemetry project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import InstrumentationBaggage
@testable import Logging
@testable import OpenTelemetry
import XCTest

final class MetadataProviderTests: XCTestCase {
func test_providesMetadataFromSpanContext_withDefaultLabels() throws {
#if swift(>=5.5) && canImport(_Concurrency)
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else {
throw XCTSkip("Task locals are not supported on this platform.")
}

let stream = InterceptingStream()
var logger = Logger(label: "test")
logger.handler = StreamLogHandler(label: "test", stream: stream, metadataProvider: .otel)

let spanContext = OTel.SpanContext(
traceID: .random(),
spanID: .random(),
traceFlags: .sampled,
isRemote: true
)

var baggage = Baggage.topLevel
baggage.spanContext = spanContext
Baggage.$current.withValue(baggage) {
logger.info("This is a test message", metadata: ["explicit": "42"])
}

XCTAssertEqual(stream.strings.count, 1)
let message = try XCTUnwrap(stream.strings.first)

XCTAssertTrue(message.contains("span-id=\(spanContext.spanID)"))
XCTAssertTrue(message.contains("trace-id=\(spanContext.traceID)"))
XCTAssertTrue(message.contains("explicit=42"))
XCTAssertTrue(message.contains("This is a test message"))
#endif
}

func test_providesMetadataFromSpanContext_withCustomLabels() throws {
#if swift(>=5.5) && canImport(_Concurrency)
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else {
throw XCTSkip("Task locals are not supported on this platform.")
}

let stream = InterceptingStream()
var logger = Logger(label: "test")
let metadataProvider = Logger.MetadataProvider.otel(traceIDKey: "custom_trace_id", spanIDKey: "custom_span_id")
logger.handler = StreamLogHandler(label: "test", stream: stream, metadataProvider: metadataProvider)

let spanContext = OTel.SpanContext(
traceID: .random(),
spanID: .random(),
traceFlags: .sampled,
isRemote: true
)

var baggage = Baggage.topLevel
baggage.spanContext = spanContext
Baggage.$current.withValue(baggage) {
logger.info("This is a test message", metadata: ["explicit": "42"])
}

XCTAssertEqual(stream.strings.count, 1)
let message = try XCTUnwrap(stream.strings.first)

XCTAssertTrue(message.contains("custom_span_id=\(spanContext.spanID)"))
XCTAssertTrue(message.contains("custom_trace_id=\(spanContext.traceID)"))
XCTAssertTrue(message.contains("explicit=42"))
XCTAssertTrue(message.contains("This is a test message"))
#endif
}

func test_doesNotProvideMetadataWithoutSpanContext() throws {
#if swift(>=5.5) && canImport(_Concurrency)
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else {
throw XCTSkip("Task locals are not supported on this platform.")
}

let stream = InterceptingStream()
var logger = Logger(label: "test")
let metadataProvider = Logger.MetadataProvider.otel
logger.handler = StreamLogHandler(label: "test", stream: stream, metadataProvider: metadataProvider)

logger.info("This is a test message", metadata: ["explicit": "42"])

XCTAssertEqual(stream.strings.count, 1)
let message = try XCTUnwrap(stream.strings.first)

XCTAssertFalse(message.contains("trace-id"))
XCTAssertFalse(message.contains("span-id"))
XCTAssertTrue(message.contains("explicit=42"))
XCTAssertTrue(message.contains("This is a test message"))
#endif
}
}

final class InterceptingStream: TextOutputStream {
var interceptedText: String?
var strings = [String]()

func write(_ string: String) {
strings.append(string)
interceptedText = (interceptedText ?? "") + string
}
}

#if compiler(>=5.6)
extension InterceptingStream: @unchecked Sendable {}
#endif
4 changes: 2 additions & 2 deletions scripts/validate_license_headers.sh
Expand Up @@ -3,7 +3,7 @@
##
## This source file is part of the Swift OpenTelemetry open source project
##
## Copyright (c) 2021 Moritz Lang and the Swift OpenTelemetry project authors
## Copyright (c) 2023 Moritz Lang and the Swift OpenTelemetry project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
Expand Down Expand Up @@ -31,7 +31,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

function replace_acceptable_years() {
# this needs to replace all acceptable forms with 'YEARS'
sed -e 's/2020-2022/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/' -e 's/2022/YEARS/'
sed -e 's/2020-2023/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/' -e 's/2022/YEARS/' -e 's/2023/YEARS/'
}

printf "=> Checking license headers\n"
Expand Down

0 comments on commit 66031a2

Please sign in to comment.