Skip to content

Commit

Permalink
Add MultiplexPropagator (slashmo#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
slashmo authored Apr 11, 2021
1 parent f8e6fe1 commit b1a5c22
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 1 deletion.
13 changes: 13 additions & 0 deletions Examples/Onboarding/Sources/Onboarding/main.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//===----------------------------------------------------------------------===//
//
// 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 NIO
import OpenTelemetry
import OtlpGRPCSpanExporting
Expand Down
58 changes: 58 additions & 0 deletions Sources/OpenTelemetry/Propagation/MultiplexPropagator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===----------------------------------------------------------------------===//
//
// 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 Instrumentation

extension OTel {
/// A pseudo-`OTelPropagator` that may be used to instrument using
/// multiple other `OTelPropagator`s across a common `OTel.SpanContext`.
public struct MultiplexPropagator: OTelPropagator {
private let propagators: [OTelPropagator]

/// Create a `MultiplexPropagator`.
///
/// ## Extraction Priority
///
/// The order of the given propagators matters when extracting!
/// If multiple propagators successfully extract, the extracted span context of
/// the last propagator called, i.e. the last propagator in the `propagators` array.
///
/// - Parameter propagators: An array of `OTelPropagator`s, each of which
/// will be used to `inject`/`extract` through the same `SpanContext`.
public init(_ propagators: [OTelPropagator]) {
self.propagators = propagators
}

public func extractSpanContext<Carrier, Extract>(
from carrier: Carrier,
using extractor: Extract
) throws -> OTel.SpanContext? where Extract: Extractor, Carrier == Extract.Carrier {
var spanContext: OTel.SpanContext?
for propagator in propagators {
guard let context = try propagator.extractSpanContext(from: carrier, using: extractor) else {
continue
}
spanContext = context
}
return spanContext
}

public func inject<Carrier, Inject>(
_ spanContext: OTel.SpanContext,
into carrier: inout Carrier,
using injector: Inject
) where Inject: Injector, Carrier == Inject.Carrier {
propagators.forEach { $0.inject(spanContext, into: &carrier, using: injector) }
}
}
}
2 changes: 1 addition & 1 deletion Sources/OpenTelemetry/Tracer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension OTel.Tracer: Instrument {
} catch {
logger.debug("Failed to extract span context", metadata: [
"carrier": .string(String(describing: carrier)),
"error": .string(String(describing: error))
"error": .string(String(describing: error)),
])
}
}
Expand Down
154 changes: 154 additions & 0 deletions Tests/OpenTelemetryTests/Propagation/MultiplexPropagatorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//===----------------------------------------------------------------------===//
//
// 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 Instrumentation
@testable import OpenTelemetry
import XCTest

final class MultiplexPropagatorTests: XCTestCase {
private let injector = DictionaryInjector()
private let extractor = DictionaryExtractor()

// MARK: - Inject

func test_invokesInjectOnAllPropagators() {
let spanContext = OTel.SpanContext(
traceID: .stub,
spanID: .stub,
traceFlags: [],
isRemote: false
)
var headers = [String: String]()

let propagator = OTel.MultiplexPropagator([SystemAPropagator(), SystemBPropagator()])
propagator.inject(spanContext, into: &headers, using: injector)

XCTAssertEqual(headers["a-trace-id"], "0102030405060708090a0b0c0d0e0f10-0102030405060708")
XCTAssertEqual(headers["b-trace-id"], "100f0e0d0c0b0a090807060504030201-0807060504030201")
}

// MARK: - Extract

func test_extractsNil_allPropagatorsReturningNil() throws {
let headers = ["c-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemAPropagator(), SystemBPropagator()])
XCTAssertNil(try propagator.extractSpanContext(from: headers, using: extractor))
}

func test_extractsSystemAHeader() throws {
let headers = ["a-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemAPropagator(), SystemBPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemAPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemAPropagator.validSpanID)
}

func test_extractsSystemAHeader_reverseOrder() throws {
let headers = ["a-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemBPropagator(), SystemAPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemAPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemAPropagator.validSpanID)
}

func test_extractsSystemBHeader() throws {
let headers = ["b-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemAPropagator(), SystemBPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemBPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemBPropagator.validSpanID)
}

func test_extractsSystemBHeader_reverseOrder() throws {
let headers = ["b-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemBPropagator(), SystemAPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemBPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemBPropagator.validSpanID)
}

func test_extractsBothSystemHeaders() throws {
let headers = ["a-trace-id": "valid", "b-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemAPropagator(), SystemBPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemBPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemBPropagator.validSpanID)
}

func test_extractsBothSystemHeaders_reverseOrder() throws {
let headers = ["a-trace-id": "valid", "b-trace-id": "valid"]

let propagator = OTel.MultiplexPropagator([SystemBPropagator(), SystemAPropagator()])
let spanContext = try XCTUnwrap(propagator.extractSpanContext(from: headers, using: extractor))

XCTAssertEqual(spanContext.traceID, SystemAPropagator.validTraceID)
XCTAssertEqual(spanContext.spanID, SystemAPropagator.validSpanID)
}
}

private struct SystemAPropagator: OTelPropagator {
static let validTraceID = OTel.TraceID(bytes: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
static let validSpanID = OTel.SpanID(bytes: (1, 2, 3, 4, 5, 6, 7, 8))

public func extractSpanContext<Carrier, Extract>(
from carrier: Carrier,
using extractor: Extract
) throws -> OTel.SpanContext? where Extract: Extractor, Carrier == Extract.Carrier {
guard let value = extractor.extract(key: "a-trace-id", from: carrier) else { return nil }
guard value == "valid" else { throw PropagatorError() }
return OTel.SpanContext(traceID: Self.validTraceID, spanID: Self.validSpanID, traceFlags: [], isRemote: true)
}

public func inject<Carrier, Inject>(
_ spanContext: OTel.SpanContext,
into carrier: inout Carrier,
using injector: Inject
) where Inject: Injector, Carrier == Inject.Carrier {
injector.inject("\(Self.validTraceID)-\(Self.validSpanID)", forKey: "a-trace-id", into: &carrier)
}
}

private struct SystemBPropagator: OTelPropagator {
static let validTraceID = OTel.TraceID(bytes: (16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))
static let validSpanID = OTel.SpanID(bytes: (8, 7, 6, 5, 4, 3, 2, 1))

public func extractSpanContext<Carrier, Extract>(
from carrier: Carrier,
using extractor: Extract
) throws -> OTel.SpanContext? where Extract: Extractor, Carrier == Extract.Carrier {
guard let value = extractor.extract(key: "b-trace-id", from: carrier) else { return nil }
guard value == "valid" else { throw PropagatorError() }
return OTel.SpanContext(traceID: Self.validTraceID, spanID: Self.validSpanID, traceFlags: [], isRemote: true)
}

public func inject<Carrier, Inject>(
_ spanContext: OTel.SpanContext,
into carrier: inout Carrier,
using injector: Inject
) where Inject: Injector, Carrier == Inject.Carrier {
injector.inject("\(Self.validTraceID)-\(Self.validSpanID)", forKey: "b-trace-id", into: &carrier)
}
}

private struct PropagatorError: Error {}

0 comments on commit b1a5c22

Please sign in to comment.