| 
 | 1 | +//===----------------------------------------------------------------------===//  | 
 | 2 | +//  | 
 | 3 | +// This source file is part of the Swift Distributed Tracing open source project  | 
 | 4 | +//  | 
 | 5 | +// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors  | 
 | 6 | +// Licensed under Apache License v2.0  | 
 | 7 | +//  | 
 | 8 | +// See LICENSE.txt for license information  | 
 | 9 | +// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors  | 
 | 10 | +//  | 
 | 11 | +// SPDX-License-Identifier: Apache-2.0  | 
 | 12 | +//  | 
 | 13 | +//===----------------------------------------------------------------------===//  | 
 | 14 | + | 
 | 15 | +@_spi(Locking) import Instrumentation  | 
 | 16 | +import Tracing  | 
 | 17 | + | 
 | 18 | +/// A ``Span`` created by the ``InMemoryTracer`` that will be retained in memory when ended.  | 
 | 19 | +/// See ``InMemoryTracer/  | 
 | 20 | +public struct InMemorySpan: Span {  | 
 | 21 | + | 
 | 22 | +    public let context: ServiceContext  | 
 | 23 | +    public var spanContext: InMemorySpanContext {  | 
 | 24 | +        context.inMemorySpanContext!  | 
 | 25 | +    }  | 
 | 26 | + | 
 | 27 | +    /// The ID of the overall trace this span belongs to.  | 
 | 28 | +    public var traceID: String {  | 
 | 29 | +        spanContext.spanID  | 
 | 30 | +    }  | 
 | 31 | +    /// The ID of this concrete span.  | 
 | 32 | +    public var spanID: String {  | 
 | 33 | +        spanContext.spanID  | 
 | 34 | +    }  | 
 | 35 | +    /// The ID of the parent span of this span, if there was any.  | 
 | 36 | +    /// When this is `nil` it means this is the top-level span of this trace.  | 
 | 37 | +    public var parentSpanID: String? {  | 
 | 38 | +        spanContext.parentSpanID  | 
 | 39 | +    }  | 
 | 40 | + | 
 | 41 | +    public let kind: SpanKind  | 
 | 42 | +    public let startInstant: any TracerInstant  | 
 | 43 | + | 
 | 44 | +    private let _operationName: LockedValueBox<String>  | 
 | 45 | +    private let _attributes = LockedValueBox<SpanAttributes>([:])  | 
 | 46 | +    private let _events = LockedValueBox<[SpanEvent]>([])  | 
 | 47 | +    private let _links = LockedValueBox<[SpanLink]>([])  | 
 | 48 | +    private let _errors = LockedValueBox<[RecordedError]>([])  | 
 | 49 | +    private let _status = LockedValueBox<SpanStatus?>(nil)  | 
 | 50 | +    private let _isRecording = LockedValueBox<Bool>(true)  | 
 | 51 | +    private let onEnd: @Sendable (FinishedInMemorySpan) -> Void  | 
 | 52 | + | 
 | 53 | +    public init(  | 
 | 54 | +        operationName: String,  | 
 | 55 | +        context: ServiceContext,  | 
 | 56 | +        spanContext: InMemorySpanContext,  | 
 | 57 | +        kind: SpanKind,  | 
 | 58 | +        startInstant: any TracerInstant,  | 
 | 59 | +        onEnd: @escaping @Sendable (FinishedInMemorySpan) -> Void  | 
 | 60 | +    ) {  | 
 | 61 | +        self._operationName = LockedValueBox(operationName)  | 
 | 62 | +        var context = context  | 
 | 63 | +        context.inMemorySpanContext = spanContext  | 
 | 64 | +        self.context = context  | 
 | 65 | +        self.kind = kind  | 
 | 66 | +        self.startInstant = startInstant  | 
 | 67 | +        self.onEnd = onEnd  | 
 | 68 | +    }  | 
 | 69 | + | 
 | 70 | +    /// The in memory span stops recording (storing mutations performed on the span) when it is ended.  | 
 | 71 | +    /// In other words, a finished span no longer is mutable and will ignore all subsequent attempts to mutate.  | 
 | 72 | +    public var isRecording: Bool {  | 
 | 73 | +        _isRecording.withValue { $0 }  | 
 | 74 | +    }  | 
 | 75 | + | 
 | 76 | +    public var operationName: String {  | 
 | 77 | +        get {  | 
 | 78 | +            _operationName.withValue { $0 }  | 
 | 79 | +        }  | 
 | 80 | +        nonmutating set {  | 
 | 81 | +            guard isRecording else { return }  | 
 | 82 | +            _operationName.withValue { $0 = newValue }  | 
 | 83 | +        }  | 
 | 84 | +    }  | 
 | 85 | + | 
 | 86 | +    public var attributes: SpanAttributes {  | 
 | 87 | +        get {  | 
 | 88 | +            _attributes.withValue { $0 }  | 
 | 89 | +        }  | 
 | 90 | +        nonmutating set {  | 
 | 91 | +            guard isRecording else { return }  | 
 | 92 | +            _attributes.withValue { $0 = newValue }  | 
 | 93 | +        }  | 
 | 94 | +    }  | 
 | 95 | + | 
 | 96 | +    public var events: [SpanEvent] {  | 
 | 97 | +        _events.withValue { $0 }  | 
 | 98 | +    }  | 
 | 99 | + | 
 | 100 | +    public func addEvent(_ event: SpanEvent) {  | 
 | 101 | +        guard isRecording else { return }  | 
 | 102 | +        _events.withValue { $0.append(event) }  | 
 | 103 | +    }  | 
 | 104 | + | 
 | 105 | +    public var links: [SpanLink] {  | 
 | 106 | +        _links.withValue { $0 }  | 
 | 107 | +    }  | 
 | 108 | + | 
 | 109 | +    public func addLink(_ link: SpanLink) {  | 
 | 110 | +        guard isRecording else { return }  | 
 | 111 | +        _links.withValue { $0.append(link) }  | 
 | 112 | +    }  | 
 | 113 | + | 
 | 114 | +    public var errors: [RecordedError] {  | 
 | 115 | +        _errors.withValue { $0 }  | 
 | 116 | +    }  | 
 | 117 | + | 
 | 118 | +    public func recordError(  | 
 | 119 | +        _ error: any Error,  | 
 | 120 | +        attributes: SpanAttributes,  | 
 | 121 | +        at instant: @autoclosure () -> some TracerInstant  | 
 | 122 | +    ) {  | 
 | 123 | +        guard isRecording else { return }  | 
 | 124 | +        _errors.withValue {  | 
 | 125 | +            $0.append(RecordedError(error: error, attributes: attributes, instant: instant()))  | 
 | 126 | +        }  | 
 | 127 | +    }  | 
 | 128 | + | 
 | 129 | +    public var status: SpanStatus? {  | 
 | 130 | +        _status.withValue { $0 }  | 
 | 131 | +    }  | 
 | 132 | + | 
 | 133 | +    public func setStatus(_ status: SpanStatus) {  | 
 | 134 | +        guard isRecording else { return }  | 
 | 135 | +        _status.withValue { $0 = status }  | 
 | 136 | +    }  | 
 | 137 | + | 
 | 138 | +    public func end(at instant: @autoclosure () -> some TracerInstant) {  | 
 | 139 | +        let shouldRecord = _isRecording.withValue {  | 
 | 140 | +            let value = $0  | 
 | 141 | +            $0 = false  // from here on after, stop recording  | 
 | 142 | +            return value  | 
 | 143 | +        }  | 
 | 144 | +        guard shouldRecord else { return }  | 
 | 145 | + | 
 | 146 | +        let finishedSpan = FinishedInMemorySpan(  | 
 | 147 | +            operationName: operationName,  | 
 | 148 | +            context: context,  | 
 | 149 | +            kind: kind,  | 
 | 150 | +            startInstant: startInstant,  | 
 | 151 | +            endInstant: instant(),  | 
 | 152 | +            attributes: attributes,  | 
 | 153 | +            events: events,  | 
 | 154 | +            links: links,  | 
 | 155 | +            errors: errors,  | 
 | 156 | +            status: status  | 
 | 157 | +        )  | 
 | 158 | +        onEnd(finishedSpan)  | 
 | 159 | +    }  | 
 | 160 | + | 
 | 161 | +    public struct RecordedError: Sendable {  | 
 | 162 | +        public let error: Error  | 
 | 163 | +        public let attributes: SpanAttributes  | 
 | 164 | +        public let instant: any TracerInstant  | 
 | 165 | +    }  | 
 | 166 | +}  | 
 | 167 | + | 
 | 168 | +/// Represents a finished span (a ``Span`` that `end()` was called on)  | 
 | 169 | +/// that was recorded by the ``InMemoryTracer``.  | 
 | 170 | +public struct FinishedInMemorySpan: Sendable {  | 
 | 171 | +    public var operationName: String  | 
 | 172 | + | 
 | 173 | +    public var context: ServiceContext  | 
 | 174 | +    public var spanContext: InMemorySpanContext {  | 
 | 175 | +        get {  | 
 | 176 | +            context.inMemorySpanContext!  | 
 | 177 | +        }  | 
 | 178 | +        set {  | 
 | 179 | +            context.inMemorySpanContext = newValue  | 
 | 180 | +        }  | 
 | 181 | +    }  | 
 | 182 | + | 
 | 183 | +    /// The ID of the overall trace this span belongs to.  | 
 | 184 | +    public var traceID: String {  | 
 | 185 | +        get {  | 
 | 186 | +            spanContext.spanID  | 
 | 187 | +        }  | 
 | 188 | +        set {  | 
 | 189 | +            spanContext.spanID = newValue  | 
 | 190 | +        }  | 
 | 191 | +    }  | 
 | 192 | +    /// The ID of this concrete span.  | 
 | 193 | +    public var spanID: String {  | 
 | 194 | +        get {  | 
 | 195 | +            spanContext.spanID  | 
 | 196 | +        }  | 
 | 197 | +        set {  | 
 | 198 | +            spanContext.spanID = newValue  | 
 | 199 | +        }  | 
 | 200 | +    }  | 
 | 201 | +    /// The ID of the parent span of this span, if there was any.  | 
 | 202 | +    /// When this is `nil` it means this is the top-level span of this trace.  | 
 | 203 | +    public var parentSpanID: String? {  | 
 | 204 | +        get {  | 
 | 205 | +            spanContext.parentSpanID  | 
 | 206 | +        }  | 
 | 207 | +        set {  | 
 | 208 | +            spanContext.parentSpanID = newValue  | 
 | 209 | +        }  | 
 | 210 | +    }  | 
 | 211 | + | 
 | 212 | +    public var kind: SpanKind  | 
 | 213 | +    public var startInstant: any TracerInstant  | 
 | 214 | +    public var endInstant: any TracerInstant  | 
 | 215 | +    public var attributes: SpanAttributes  | 
 | 216 | +    public var events: [SpanEvent]  | 
 | 217 | +    public var links: [SpanLink]  | 
 | 218 | +    public var errors: [InMemorySpan.RecordedError]  | 
 | 219 | +    public var status: SpanStatus?  | 
 | 220 | +}  | 
0 commit comments