diff --git a/README.md b/README.md index cd38560..6851c79 100644 --- a/README.md +++ b/README.md @@ -188,10 +188,41 @@ public protocol XRayEmitter { } ``` -example: +example of an emitter which logs emitted segments: ```swift -let recorder = XRayRecorder(emitter: XRayNoopEmitter()) +public struct XRayLogEmitter: XRayEmitter { + private let logger: Logger + + private let encoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + return encoder + }() + + public init(label: String? = nil) { + let label = label ?? "xray.log_emitter.\(String.random32())" + logger = Logger(label: label) + } + + public func send(_ segment: XRayRecorder.Segment) { + do { + let document: String = try encoder.encode(segment) + logger.info("\n\(document)") + } catch { + logger.error("Failed to encode a segment: \(error)") + } + } + + public func flush(_: @escaping (Error?) -> Void) {} +} +``` + + +The emitter has to be provided when creating an instance of `XRayRecorder`: + +```swift +let recorder = XRayRecorder(emitter: XRayNoOpEmitter()) ``` ## References diff --git a/Sources/AWSXRayRecorder/Config.swift b/Sources/AWSXRayRecorder/Config.swift index 9c990fc..1d73f6c 100644 --- a/Sources/AWSXRayRecorder/Config.swift +++ b/Sources/AWSXRayRecorder/Config.swift @@ -1,13 +1,7 @@ import Logging -import NIO // getenv // TODO: document -private func env(_ name: String) -> String? { - guard let value = getenv(name) else { return nil } - return String(cString: value) -} - public extension XRayRecorder { struct Config { let enabled: Bool @@ -46,3 +40,21 @@ internal extension XRayUDPEmitter { } } } + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +private func env(_ name: String) -> String? { + #if canImport(Darwin) + guard let value = getenv(name) else { return nil } + return String(cString: value) + #elseif canImport(Glibc) + guard let value = getenv(name) else { return nil } + return String(cString: value) + #else + return nil + #endif +} diff --git a/Sources/AWSXRayRecorder/Emitter.swift b/Sources/AWSXRayRecorder/Emitter.swift index 19bb240..bfce5ad 100644 --- a/Sources/AWSXRayRecorder/Emitter.swift +++ b/Sources/AWSXRayRecorder/Emitter.swift @@ -5,7 +5,7 @@ public protocol XRayEmitter { func flush(_ callback: @escaping (Error?) -> Void) } -public struct XRayNoopEmitter: XRayEmitter { +public struct XRayNoOpEmitter: XRayEmitter { public func send(_: XRayRecorder.Segment) {} public func flush(_: @escaping (Error?) -> Void) {} diff --git a/Sources/AWSXRayRecorder/LogEmitter/LogEmitter.swift b/Sources/AWSXRayRecorder/LogEmitter/LogEmitter.swift new file mode 100644 index 0000000..69604db --- /dev/null +++ b/Sources/AWSXRayRecorder/LogEmitter/LogEmitter.swift @@ -0,0 +1,38 @@ +import Logging + +import struct Foundation.Data +import class Foundation.JSONEncoder + +private extension JSONEncoder { + func encode(_ value: T) throws -> String { + String(decoding: try encode(value), as: UTF8.self) + } +} + +// TODO: document + +public struct XRayLogEmitter: XRayEmitter { + private let logger: Logger + + private let encoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + return encoder + }() + + public init(label: String? = nil) { + let label = label ?? "xray.log_emitter.\(String.random32())" + logger = Logger(label: label) + } + + public func send(_ segment: XRayRecorder.Segment) { + do { + let document: String = try encoder.encode(segment) + logger.info("\n\(document)") + } catch { + logger.error("Failed to encode a segment: \(error)") + } + } + + public func flush(_: @escaping (Error?) -> Void) {} +} diff --git a/Sources/AWSXRayRecorder/Recorder.swift b/Sources/AWSXRayRecorder/Recorder.swift index c72b53c..a9031f9 100644 --- a/Sources/AWSXRayRecorder/Recorder.swift +++ b/Sources/AWSXRayRecorder/Recorder.swift @@ -23,7 +23,7 @@ public class XRayRecorder { public init(emitter: XRayEmitter, config: Config = Config()) { self.config = config if !config.enabled { - self.emitter = XRayNoopEmitter() + self.emitter = XRayNoOpEmitter() } else { self.emitter = emitter } @@ -32,7 +32,7 @@ public class XRayRecorder { public convenience init(config: Config = Config(), eventLoopGroup: EventLoopGroup? = nil) { if !config.enabled { - self.init(emitter: XRayNoopEmitter(), config: config) + self.init(emitter: XRayNoOpEmitter(), config: config) } else { do { let emitter = try XRayUDPEmitter(config: .init(config), eventLoopGroup: eventLoopGroup) @@ -116,17 +116,19 @@ public class XRayRecorder { logger.debug("Segment \(id) parent has not been sent") return } - // mark it as emitted and pass responsibility to the emitter to actually do so do { + // mark it as emitted and pass responsibility to the emitter to actually do so try segment.emit() + // check if any of its subsegments are in progress and keep them in the recorder + let subsegments = segment.subsegmentsInProgress() + logger.debug("Segment \(id) has \(subsegments.count) subsegments \(Segment.State.inProgress)") + segmentsLock.withWriterLock { + subsegments.forEach { _segments[$0.id] = $0 } + } + // pass if the the emitter + emitter.send(segment) } catch { logger.error("Failed to emit Segment \(id): \(error)") } - // check if any of its subsegments are in progress and keep them in the recorder - let subsegments = segment.subsegmentsInProgress() - logger.debug("Segment \(id) has \(subsegments.count) subsegments \(Segment.State.inProgress)") - subsegments.forEach { _segments[$0.id] = $0 } - // pass if the the emitter - emitter.send(segment) } }