Skip to content

Commit

Permalink
Add X-Ray specific MetadataProvider. (#14)
Browse files Browse the repository at this point in the history
* Add X-Ray specific MetadataProvider.

* Added MetadataProvider unit tests.
  • Loading branch information
tachyonics authored Sep 3, 2023
1 parent 11bfb3a commit 2a15f21
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
42 changes: 42 additions & 0 deletions Sources/OpenTelemetryXRay/MetadataProvider+OTelXRay.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenTelemetry open source project
//
// Copyright (c) 2021 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 Logging
import ServiceContextModule

@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 using X-Ray specific formatting.
///
/// - 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 otelXRay(traceIDKey: String = "trace-id", spanIDKey: String = "span-id") -> Logger.MetadataProvider {
.init {
guard let spanContext = ServiceContext.current?.spanContext else { return [:] }

let traceIDBytes = spanContext.traceID.hexBytes
let timestampBytes = traceIDBytes[0 ..< 8]
let randomBytes = traceIDBytes[8...]

return [
traceIDKey: "1-\(String(decoding: timestampBytes, as: UTF8.self))-\(String(decoding: randomBytes, as: UTF8.self))",
spanIDKey: "\(spanContext.spanID)",
]
}
}

/// A metadata provider exposing the current trace and span ID using X-Ray specific formatting.
public static let otelXRay = Logger.MetadataProvider.otelXRay()
}
130 changes: 130 additions & 0 deletions Tests/OpenTelemetryXRayTests/MetadataProviderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenTelemetry open source project
//
// Copyright (c) 2021 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
//
//===----------------------------------------------------------------------===//

@testable import Logging
@testable import OpenTelemetryXRay
@testable import OpenTelemetry
import ServiceContextModule
import XCTest

final class MetadataProviderTests: XCTestCase {
func test_providesMetadataFromSpanContext_withDefaultLabels() throws {
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: .otelXRay)

var generator = XRayIDGenerator()

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

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

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

let traceIDBytes = spanContext.traceID.hexBytes
let timestampBytes = traceIDBytes[0 ..< 8]
let randomBytes = traceIDBytes[8...]
let expectedTraceId = "1-\(String(decoding: timestampBytes, as: UTF8.self))-\(String(decoding: randomBytes, as: UTF8.self))"

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

func test_providesMetadataFromSpanContext_withCustomLabels() throws {
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.otelXRay(traceIDKey: "custom_trace_id", spanIDKey: "custom_span_id")
logger.handler = StreamLogHandler(label: "test", stream: stream, metadataProvider: metadataProvider)

var generator = XRayIDGenerator()

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

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

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

let traceIDBytes = spanContext.traceID.hexBytes
let timestampBytes = traceIDBytes[0 ..< 8]
let randomBytes = traceIDBytes[8...]
let expectedTraceId = "1-\(String(decoding: timestampBytes, as: UTF8.self))-\(String(decoding: randomBytes, as: UTF8.self))"

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

func test_doesNotProvideMetadataWithoutSpanContext() throws {
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.otelXRay
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"))
}
}

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

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

extension InterceptingStream: @unchecked Sendable {}

0 comments on commit 2a15f21

Please sign in to comment.