Skip to content

Documentation with latest guidance, and adjust types accordingly #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
571 changes: 27 additions & 544 deletions README.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Samples/Dinner/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.build
Package.resolved
23 changes: 23 additions & 0 deletions Samples/Dinner/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version:5.3
import PackageDescription

let package = Package(
name: "onboarding",
platforms: [
.macOS("14.0.0"),
],
products: [
.executable(name: "onboarding", targets: ["Onboarding"]),
],
dependencies: [
// This example uses the following tracer implementation:
.package(url: "https://github.com/slashmo/opentelemetry-swift", .branch("main")),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
],
targets: [
.target(name: "Onboarding", dependencies: [
.product(name: "OpenTelemetry", package: "opentelemetry-swift"),
.product(name: "OtlpGRPCSpanExporting", package: "opentelemetry-swift"),
]),
]
)
18 changes: 18 additions & 0 deletions Samples/Dinner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Tracing Dinner Sample App

In order to try this sample app, and visualize traces it produces, you should first run docker-compose in order
to launch a docker containers which host a Zipkin UI and collector:

```
# cd Samples/Dinner

docker-compose -f docker/docker-compose.yaml up --build
```

and then run the sample app which will produce a number of traces:

```
swift run -c release
```

Refer to the "Trace Your Application" guide in the documentation to learn more about how to interpret this sample.
16 changes: 16 additions & 0 deletions Samples/Dinner/Sources/Onboarding/Clock+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +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
//
//===----------------------------------------------------------------------===//

func sleep(for duration: ContinuousClock.Duration) async {
try? await Task.sleep(until: ContinuousClock.now + duration, clock: .continuous)
}
76 changes: 76 additions & 0 deletions Samples/Dinner/Sources/Onboarding/Dinner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
//
// 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 Tracing

func makeDinner() async throws -> Meal {
try await InstrumentationSystem.tracer.withSpan("makeDinner") { _ in
await sleep(for: .milliseconds(200))

async let veggies = try chopVegetables()
async let meat = marinateMeat()
async let oven = preheatOven(temperature: 350)
// ...
return try await cook(veggies, meat, oven)
}
}

func chopVegetables() async throws -> [Vegetable] {
try await otelChopping1.tracer().withSpan("chopVegetables") { _ in
// Chop the vegetables...!
//
// However, since chopping is a very difficult operation,
// one chopping task can be performed at the same time on a single service!
// (Imagine that... we cannot parallelize these two tasks, and need to involve another service).
async let carrot = try chop(.carrot, tracer: otelChopping1.tracer())
async let potato = try chop(.potato, tracer: otelChopping2.tracer())
return try await [carrot, potato]
}
}

func chop(_ vegetable: Vegetable, tracer: any Tracer) async throws -> Vegetable {
await tracer.withSpan("chop-\(vegetable)") { _ in
await sleep(for: .seconds(5))
// ...
return vegetable // "chopped"
}
}

func marinateMeat() async -> Meat {
await sleep(for: .milliseconds(620))

return await InstrumentationSystem.tracer.withSpan("marinateMeat") { _ in
await sleep(for: .seconds(3))
// ...
return Meat()
}
}

func preheatOven(temperature: Int) async -> Oven {
await InstrumentationSystem.tracer.withSpan("preheatOven") { _ in
// ...
await sleep(for: .seconds(6))
return Oven()
}
}

func cook(_: Any, _: Any, _: Any) async -> Meal {
await InstrumentationSystem.tracer.withSpan("cook") { span in
span.addEvent("children-asking-if-done-already")
await sleep(for: .seconds(3))
span.addEvent("children-asking-if-done-already-again")
await sleep(for: .seconds(2))
// ...
return Meal()
}
}
21 changes: 21 additions & 0 deletions Samples/Dinner/Sources/Onboarding/Model.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

struct Meal: Sendable {}
struct Meat: Sendable {}
struct Oven: Sendable {}
enum Vegetable: Sendable {
case potato
case carrot
}

63 changes: 63 additions & 0 deletions Samples/Dinner/Sources/Onboarding/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
//
// 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 NIO
import OpenTelemetry
import OtlpGRPCSpanExporting
import Tracing

// ==== ----------------------------------------------------------------------------------------------------------------

let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .trace
return handler
}

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: - Configure OTel

let exporter = OtlpGRPCSpanExporter(config: OtlpGRPCSpanExporter.Config(eventLoopGroup: group))
let processor = OTel.SimpleSpanProcessor(exportingTo: exporter)
let otel = OTel(serviceName: "DinnerService", eventLoopGroup: group, processor: processor)

let otelChopping1 = OTel(serviceName: "ChoppingService-1", eventLoopGroup: group, processor: processor)
let otelChopping2 = OTel(serviceName: "ChoppingService-2", eventLoopGroup: group, processor: processor)

// First start `OTel`, then bootstrap the instrumentation system.
// This makes sure that all components are ready to begin handling spans.
try otel.start().wait()
try otelChopping1.start().wait()
try otelChopping2.start().wait()

// By bootstrapping the instrumentation system, our dependencies
// compatible with "Swift Distributed Tracing" will also automatically
// use the "OpenTelemetry Swift" Tracer 🚀.
InstrumentationSystem.bootstrap(otel.tracer())

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: - Run the sample app

let dinner = try await makeDinner()

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: - Shutdown

// Wait a second to let the exporter finish before shutting down.
sleep(2)

try otel.shutdown().wait()
try group.syncShutdownGracefully()
24 changes: 24 additions & 0 deletions Samples/Dinner/docker/collector-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
receivers:
otlp:
protocols:
grpc:
endpoint: otel-collector:4317

exporters:
logging:
logLevel: debug

jaeger:
endpoint: "jaeger:14250"
tls:
insecure: true

zipkin:
endpoint: "http://zipkin:9411/api/v2/spans"


service:
pipelines:
traces:
receivers: otlp
exporters: [logging, jaeger, zipkin]
26 changes: 26 additions & 0 deletions Samples/Dinner/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3'
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/config.yaml"]
volumes:
- ./collector-config.yaml:/etc/config.yaml
ports:
- "4317:4317"
networks: [exporter]
depends_on: [zipkin, jaeger]

zipkin:
image: openzipkin/zipkin:latest
ports:
- "9411:9411"
networks: [exporter]

jaeger:
image: jaegertracing/all-in-one
ports:
- "16686:16686"
networks: [exporter]

networks:
exporter:
79 changes: 58 additions & 21 deletions Sources/Instrumentation/Instrument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,46 @@

import InstrumentationBaggage

/// Conforming types are used to extract values from a specific `Carrier`.
public protocol Extractor {
/// The carrier to extract values from.
associatedtype Carrier
public enum Instrument {

/// Extract the value for the given key from the `Carrier`.
/// Convenience to access the globally bootstrapped instrument on ``InstrumentationSystem``.
///
/// - Parameters:
/// - key: The key to be extracted.
/// - carrier: The `Carrier` to extract from.
func extract(key: String, from carrier: Carrier) -> String?
}
/// Equivalent to ``InstrumentationSystem/instrument``.
static var current: InstrumentProtocol {
InstrumentationSystem.instrument
}

/// Conforming types are used to inject values into a specific `Carrier`.
public protocol Injector {
/// The carrier to inject values into.
associatedtype Carrier
/// Obtain the ``current`` instrument which was bootstrapped on the global ``InstrumentationSystem``,
/// and invoke ``InstrumentProtocol/extract(_:into:using:)`` on it.
///
/// Extract values from a `Carrier` by using the given extractor and inject them into the given `Baggage`.
/// It's quite common for `Instrument`s to come up with new values if they weren't passed along in the given `Carrier`.
///
/// - Parameters:
/// - carrier: The `Carrier` that was used to propagate values across boundaries.
/// - baggage: The `Baggage` into which these values should be injected.
/// - extractor: The ``Extractor`` that extracts values from the given `Carrier`.
public static func extract<Carrier, Extract>(_ carrier: Carrier, into baggage: inout Baggage, using extractor: Extract) where Extract: Extractor, Extract.Carrier == Carrier {
Instrument.current.extract(carrier, into: &baggage, using: extractor)
}

/// Inject the given value for the given key into the given `Carrier`.
/// Obtain the ``current`` instrument which was bootstrapped on the global ``InstrumentationSystem``,
/// and invoke ``InstrumentProtocol/extract(_:into:using:)`` on it.
///
/// Take values from a `Baggage` and inject them into the given `Carrier` using the given ``Injector``.
///
/// - Parameters:
/// - value: The value to be injected.
/// - key: The key for which to inject the value.
/// - carrier: The `Carrier` to inject into.
func inject(_ value: String, forKey key: String, into carrier: inout Carrier)
/// - baggage: The `Baggage` from which relevant information will be extracted.
/// - carrier: The `Carrier` into which this information will be injected.
/// - injector: The ``Injector`` used to inject extracted `Baggage` into the given `Carrier`.
public static func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject) where Inject: Injector, Inject.Carrier == Carrier {
Instrument.current.inject(baggage, into: &carrier, using: injector)
}
}

/// Conforming types are usually cross-cutting tools like tracers. They are agnostic of what specific `Carrier` is used
/// to propagate metadata across boundaries, but instead just specify what values to use for which keys.
public protocol Instrument {
public protocol InstrumentProtocol {
/// Extract values from a `Carrier` by using the given extractor and inject them into the given `Baggage`.
/// It's quite common for `Instrument`s to come up with new values if they weren't passed along in the given `Carrier`.
///
Expand All @@ -54,7 +64,7 @@ public protocol Instrument {
func extract<Carrier, Extract>(_ carrier: Carrier, into baggage: inout Baggage, using extractor: Extract)
where Extract: Extractor, Extract.Carrier == Carrier

/// Extract values from a `Baggage` and inject them into the given `Carrier` using the given ``Injector``.
/// Take values from a `Baggage` and inject them into the given `Carrier` using the given ``Injector``.
///
/// - Parameters:
/// - baggage: The `Baggage` from which relevant information will be extracted.
Expand All @@ -63,3 +73,30 @@ public protocol Instrument {
func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject)
where Inject: Injector, Inject.Carrier == Carrier
}

/// Conforming types are used to extract values from a specific `Carrier`.
public protocol Extractor {
/// The carrier to extract values from.
associatedtype Carrier

/// Extract the value for the given key from the `Carrier`.
///
/// - Parameters:
/// - key: The key to be extracted.
/// - carrier: The `Carrier` to extract from.
func extract(key: String, from carrier: Carrier) -> String?
}

/// Conforming types are used to inject values into a specific `Carrier`.
public protocol Injector {
/// The carrier to inject values into.
associatedtype Carrier

/// Inject the given value for the given key into the given `Carrier`.
///
/// - Parameters:
/// - value: The value to be injected.
/// - key: The key for which to inject the value.
/// - carrier: The `Carrier` to inject into.
func inject(_ value: String, forKey key: String, into carrier: inout Carrier)
}
Loading