@@ -120,7 +120,7 @@ public final class Span: Identifiable {
120120 status = . ok
121121 }
122122
123- /// Record an error into this span
123+ /// Record an error into the span.
124124 /// - Parameters:
125125 /// - error: any error object -- NSErrors have special handling to capture domain and code.
126126 /// - includeBacktrace: whether to include a backtrace. This defaults to false, and is costly at runtime.
@@ -134,6 +134,23 @@ public final class Span: Identifiable {
134134 status = . error( message: message) // this duplicates exception.message, but makes the reporting work better
135135 }
136136
137+ /// Record an error with a given message into the span.
138+ /// - Parameters:
139+ /// - type: a type of an error.
140+ /// - message: an error message to record.
141+ /// - includeBacktrace: whether to include a backtrace. This defaults to false, and is costly at runtime.
142+ public func recordError( withType type: String , message: String , includeBacktrace: Bool = false ) {
143+ let attributes = Self . exceptionAttributes (
144+ type: type,
145+ message: message,
146+ stacktrace: includeBacktrace ? Self . captureStacktrace ( ) : nil
147+ )
148+
149+ let exceptionEvent = Event ( name: " exception " , attributes: attributes)
150+ addEvent ( exceptionEvent)
151+ status = . error( message: message)
152+ }
153+
137154 // MARK: Internal
138155
139156 let name : String
@@ -154,43 +171,53 @@ public final class Span: Identifiable {
154171 }
155172
156173 static func exceptionAttributes( _ error: any Error , includeBacktrace: Bool ) -> [ String : String ] {
157- var attributes : [ String : String ]
174+ let exceptionType : String
175+ let exceptionMessage : String
158176
159177 // All swift errors bridge to NSError, so instead check the type explicitly
160178 if type ( of: error) is NSError . Type {
161179 // a "real" NSError
162180 let nsError = error as NSError
163- let message = ( error as NSError ) . localizedDescription
164- attributes = [
165- // OpenTelemetry doesn't have the concept of error codes. Pack it in exception.type.
166- " exception.type " : " NSError. \( nsError. domain) . \( nsError. code) " ,
167- " exception.message " : message,
168- ]
181+ // OpenTelemetry doesn't have the concept of error codes. Pack it in exception.type.
182+ exceptionType = " NSError. \( nsError. domain) . \( nsError. code) "
183+ exceptionMessage = ( error as NSError ) . localizedDescription
169184 } else {
170- let message = String ( describing: error)
171- attributes = [
172- " exception.type " : String ( reflecting: type ( of: error) ) ,
173- " exception.message " : message,
174- ]
175- }
176-
177- if includeBacktrace {
178- // TBD: figure out proper backtracing and Swift symbol demangling?
179- // This doesn't seem to exist yet: https://forums.swift.org/t/demangle-function/25416
180- // This looks OK, but is ≈9K lines: https://github.com/oozoofrog/SwiftDemangle
181- // Will try this, once it lands as public API:
182- // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0419-backtrace-api.md
183- let callStackLimit = 20
184- let callstackSymbols = Thread . callStackSymbols. prefix ( callStackLimit)
185- let callstack = callstackSymbols. joined ( separator: " \n " )
186- attributes [ " exception.stacktrace " ] = callstack
185+ exceptionType = String ( reflecting: type ( of: error) )
186+ exceptionMessage = String ( describing: error)
187187 }
188188
189189 // nsError.underlyingErrors contains lower-level info for network errors and may be interesting here
190190
191+ return exceptionAttributes (
192+ type: exceptionType,
193+ message: exceptionMessage,
194+ stacktrace: includeBacktrace ? captureStacktrace ( ) : nil
195+ )
196+ }
197+
198+ private static func exceptionAttributes( type: String , message: String , stacktrace: String ? ) -> [ String : String ] {
199+ var attributes = [
200+ " exception.type " : type,
201+ " exception.message " : message
202+ ]
203+ if let stacktrace {
204+ attributes [ " exception.stacktrace " ] = stacktrace
205+ }
206+
191207 return attributes
192208 }
193209
210+ private static func captureStacktrace( ) -> String {
211+ // TBD: figure out proper backtracing and Swift symbol demangling?
212+ // This doesn't seem to exist yet: https://forums.swift.org/t/demangle-function/25416
213+ // This looks OK, but is ≈9K lines: https://github.com/oozoofrog/SwiftDemangle
214+ // Will try this, once it lands as public API:
215+ // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0419-backtrace-api.md
216+ let callStackLimit = 20
217+ let callstackSymbols = Thread . callStackSymbols. prefix ( callStackLimit)
218+ return callstackSymbols. joined ( separator: " \n " )
219+ }
220+
194221 /// Records a result. This convenience method records either a success or an error,
195222 /// depending on the given result.
196223 /// - Parameters:
0 commit comments