TracingTestsUtilities.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. * Copyright 2024, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import GRPCCore
  17. import NIOConcurrencyHelpers
  18. import Tracing
  19. final class TestTracer: Tracer {
  20. typealias Span = TestSpan
  21. private var testSpans: NIOLockedValueBox<[String: TestSpan]> = .init([:])
  22. func getEventsForTestSpan(ofOperationName operationName: String) -> [SpanEvent] {
  23. self.testSpans.withLockedValue({ $0[operationName] })?.events ?? []
  24. }
  25. func extract<Carrier, Extract>(
  26. _ carrier: Carrier,
  27. into context: inout ServiceContextModule.ServiceContext,
  28. using extractor: Extract
  29. ) where Carrier == Extract.Carrier, Extract: Instrumentation.Extractor {
  30. let traceID = extractor.extract(key: TraceID.keyName, from: carrier)
  31. context[TraceID.self] = traceID
  32. }
  33. func inject<Carrier, Inject>(
  34. _ context: ServiceContextModule.ServiceContext,
  35. into carrier: inout Carrier,
  36. using injector: Inject
  37. ) where Carrier == Inject.Carrier, Inject: Instrumentation.Injector {
  38. if let traceID = context.traceID {
  39. injector.inject(traceID, forKey: TraceID.keyName, into: &carrier)
  40. }
  41. }
  42. func forceFlush() {
  43. // no-op
  44. }
  45. func startSpan<Instant>(
  46. _ operationName: String,
  47. context: @autoclosure () -> ServiceContext,
  48. ofKind kind: SpanKind,
  49. at instant: @autoclosure () -> Instant,
  50. function: String,
  51. file fileID: String,
  52. line: UInt
  53. ) -> TestSpan where Instant: TracerInstant {
  54. return self.testSpans.withLockedValue { testSpans in
  55. let span = TestSpan(context: context(), operationName: operationName)
  56. testSpans[operationName] = span
  57. return span
  58. }
  59. }
  60. }
  61. class TestSpan: Span {
  62. var context: ServiceContextModule.ServiceContext
  63. var operationName: String
  64. var attributes: Tracing.SpanAttributes
  65. var isRecording: Bool
  66. private(set) var status: Tracing.SpanStatus?
  67. private(set) var events: [Tracing.SpanEvent] = []
  68. init(
  69. context: ServiceContextModule.ServiceContext,
  70. operationName: String,
  71. attributes: Tracing.SpanAttributes = [:],
  72. isRecording: Bool = true
  73. ) {
  74. self.context = context
  75. self.operationName = operationName
  76. self.attributes = attributes
  77. self.isRecording = isRecording
  78. }
  79. func setStatus(_ status: Tracing.SpanStatus) {
  80. self.status = status
  81. }
  82. func addEvent(_ event: Tracing.SpanEvent) {
  83. self.events.append(event)
  84. }
  85. func recordError<Instant>(
  86. _ error: any Error,
  87. attributes: Tracing.SpanAttributes,
  88. at instant: @autoclosure () -> Instant
  89. ) where Instant: Tracing.TracerInstant {
  90. self.setStatus(
  91. .init(
  92. code: .error,
  93. message: "Error: \(error), attributes: \(attributes), at instant: \(instant())"
  94. )
  95. )
  96. }
  97. func addLink(_ link: Tracing.SpanLink) {
  98. self.context.spanLinks?.append(link)
  99. }
  100. func end<Instant>(at instant: @autoclosure () -> Instant) where Instant: Tracing.TracerInstant {
  101. self.setStatus(.init(code: .ok, message: "Ended at instant: \(instant())"))
  102. }
  103. }
  104. enum TraceID: ServiceContextModule.ServiceContextKey {
  105. typealias Value = String
  106. static let keyName = "trace-id"
  107. }
  108. enum ServiceContextSpanLinksKey: ServiceContextModule.ServiceContextKey {
  109. typealias Value = [SpanLink]
  110. static let keyName = "span-links"
  111. }
  112. extension ServiceContext {
  113. var traceID: String? {
  114. get {
  115. self[TraceID.self]
  116. }
  117. set {
  118. self[TraceID.self] = newValue
  119. }
  120. }
  121. var spanLinks: [SpanLink]? {
  122. get {
  123. self[ServiceContextSpanLinksKey.self]
  124. }
  125. set {
  126. self[ServiceContextSpanLinksKey.self] = newValue
  127. }
  128. }
  129. }
  130. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  131. struct TestWriter<WriterElement>: RPCWriterProtocol {
  132. typealias Element = WriterElement
  133. private let streamContinuation: AsyncStream<Element>.Continuation
  134. init(streamContinuation: AsyncStream<Element>.Continuation) {
  135. self.streamContinuation = streamContinuation
  136. }
  137. func write(contentsOf elements: some Sequence<Self.Element>) async throws {
  138. elements.forEach { element in
  139. self.streamContinuation.yield(element)
  140. }
  141. }
  142. }
  143. #if swift(<5.9)
  144. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  145. extension AsyncStream {
  146. static func makeStream(
  147. of elementType: Element.Type = Element.self,
  148. bufferingPolicy limit: AsyncStream<Element>.Continuation.BufferingPolicy = .unbounded
  149. ) -> (stream: AsyncStream<Element>, continuation: AsyncStream<Element>.Continuation) {
  150. var continuation: AsyncStream<Element>.Continuation!
  151. let stream = AsyncStream(Element.self, bufferingPolicy: limit) {
  152. continuation = $0
  153. }
  154. return (stream, continuation)
  155. }
  156. }
  157. #endif