@effect/opentelemetry 0.60.0 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +1 -1
  2. package/dist/{dts/Logger.d.ts → Logger.d.ts} +19 -13
  3. package/dist/Logger.d.ts.map +1 -0
  4. package/dist/Logger.js +76 -0
  5. package/dist/Logger.js.map +1 -0
  6. package/dist/Metrics.d.ts +76 -0
  7. package/dist/Metrics.d.ts.map +1 -0
  8. package/dist/Metrics.js +59 -0
  9. package/dist/Metrics.js.map +1 -0
  10. package/dist/{dts/NodeSdk.d.ts → NodeSdk.d.ts} +12 -9
  11. package/dist/NodeSdk.d.ts.map +1 -0
  12. package/dist/{esm/NodeSdk.js → NodeSdk.js} +23 -14
  13. package/dist/NodeSdk.js.map +1 -0
  14. package/dist/{dts/Resource.d.ts → Resource.d.ts} +10 -13
  15. package/dist/Resource.d.ts.map +1 -0
  16. package/dist/{esm/Resource.js → Resource.js} +12 -13
  17. package/dist/Resource.js.map +1 -0
  18. package/dist/Tracer.d.ts +129 -0
  19. package/dist/Tracer.d.ts.map +1 -0
  20. package/dist/Tracer.js +391 -0
  21. package/dist/Tracer.js.map +1 -0
  22. package/dist/{dts/WebSdk.d.ts → WebSdk.d.ts} +12 -9
  23. package/dist/WebSdk.d.ts.map +1 -0
  24. package/dist/WebSdk.js +41 -0
  25. package/dist/WebSdk.js.map +1 -0
  26. package/dist/index.d.ts +28 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/{esm/index.js → index.js} +4 -20
  29. package/dist/index.js.map +1 -0
  30. package/dist/internal/attributes.d.ts +2 -0
  31. package/dist/internal/attributes.d.ts.map +1 -0
  32. package/dist/internal/attributes.js +19 -0
  33. package/dist/internal/attributes.js.map +1 -0
  34. package/dist/{dts/internal → internal}/metrics.d.ts.map +1 -1
  35. package/dist/internal/metrics.js +406 -0
  36. package/dist/internal/metrics.js.map +1 -0
  37. package/dist/internal/utilities.d.ts +2 -0
  38. package/dist/internal/utilities.d.ts.map +1 -0
  39. package/dist/internal/utilities.js +3 -0
  40. package/dist/internal/utilities.js.map +1 -0
  41. package/package.json +70 -118
  42. package/src/Logger.ts +52 -55
  43. package/src/Metrics.ts +92 -18
  44. package/src/NodeSdk.ts +67 -64
  45. package/src/Resource.ts +16 -24
  46. package/src/Tracer.ts +469 -78
  47. package/src/WebSdk.ts +59 -51
  48. package/src/index.ts +7 -26
  49. package/src/internal/attributes.ts +21 -0
  50. package/src/internal/metrics.ts +381 -250
  51. package/src/internal/utilities.ts +5 -0
  52. package/Logger/package.json +0 -6
  53. package/Metrics/package.json +0 -6
  54. package/NodeSdk/package.json +0 -6
  55. package/Otlp/package.json +0 -6
  56. package/OtlpLogger/package.json +0 -6
  57. package/OtlpMetrics/package.json +0 -6
  58. package/OtlpResource/package.json +0 -6
  59. package/OtlpTracer/package.json +0 -6
  60. package/Resource/package.json +0 -6
  61. package/Tracer/package.json +0 -6
  62. package/WebSdk/package.json +0 -6
  63. package/dist/cjs/Logger.js +0 -85
  64. package/dist/cjs/Logger.js.map +0 -1
  65. package/dist/cjs/Metrics.js +0 -24
  66. package/dist/cjs/Metrics.js.map +0 -1
  67. package/dist/cjs/NodeSdk.js +0 -53
  68. package/dist/cjs/NodeSdk.js.map +0 -1
  69. package/dist/cjs/Otlp.js +0 -46
  70. package/dist/cjs/Otlp.js.map +0 -1
  71. package/dist/cjs/OtlpLogger.js +0 -158
  72. package/dist/cjs/OtlpLogger.js.map +0 -1
  73. package/dist/cjs/OtlpMetrics.js +0 -354
  74. package/dist/cjs/OtlpMetrics.js.map +0 -1
  75. package/dist/cjs/OtlpResource.js +0 -136
  76. package/dist/cjs/OtlpResource.js.map +0 -1
  77. package/dist/cjs/OtlpTracer.js +0 -229
  78. package/dist/cjs/OtlpTracer.js.map +0 -1
  79. package/dist/cjs/Resource.js +0 -75
  80. package/dist/cjs/Resource.js.map +0 -1
  81. package/dist/cjs/Tracer.js +0 -87
  82. package/dist/cjs/Tracer.js.map +0 -1
  83. package/dist/cjs/WebSdk.js +0 -42
  84. package/dist/cjs/WebSdk.js.map +0 -1
  85. package/dist/cjs/index.js +0 -30
  86. package/dist/cjs/index.js.map +0 -1
  87. package/dist/cjs/internal/metrics.js +0 -288
  88. package/dist/cjs/internal/metrics.js.map +0 -1
  89. package/dist/cjs/internal/otlpExporter.js +0 -81
  90. package/dist/cjs/internal/otlpExporter.js.map +0 -1
  91. package/dist/cjs/internal/tracer.js +0 -299
  92. package/dist/cjs/internal/tracer.js.map +0 -1
  93. package/dist/cjs/internal/utils.js +0 -34
  94. package/dist/cjs/internal/utils.js.map +0 -1
  95. package/dist/dts/Logger.d.ts.map +0 -1
  96. package/dist/dts/Metrics.d.ts +0 -29
  97. package/dist/dts/Metrics.d.ts.map +0 -1
  98. package/dist/dts/NodeSdk.d.ts.map +0 -1
  99. package/dist/dts/Otlp.d.ts +0 -31
  100. package/dist/dts/Otlp.d.ts.map +0 -1
  101. package/dist/dts/OtlpLogger.d.ts +0 -46
  102. package/dist/dts/OtlpLogger.d.ts.map +0 -1
  103. package/dist/dts/OtlpMetrics.d.ts +0 -40
  104. package/dist/dts/OtlpMetrics.d.ts.map +0 -1
  105. package/dist/dts/OtlpResource.d.ts +0 -104
  106. package/dist/dts/OtlpResource.d.ts.map +0 -1
  107. package/dist/dts/OtlpTracer.d.ts +0 -49
  108. package/dist/dts/OtlpTracer.d.ts.map +0 -1
  109. package/dist/dts/Resource.d.ts.map +0 -1
  110. package/dist/dts/Tracer.d.ts +0 -143
  111. package/dist/dts/Tracer.d.ts.map +0 -1
  112. package/dist/dts/WebSdk.d.ts.map +0 -1
  113. package/dist/dts/index.d.ts +0 -45
  114. package/dist/dts/index.d.ts.map +0 -1
  115. package/dist/dts/internal/otlpExporter.d.ts +0 -2
  116. package/dist/dts/internal/otlpExporter.d.ts.map +0 -1
  117. package/dist/dts/internal/tracer.d.ts +0 -2
  118. package/dist/dts/internal/tracer.d.ts.map +0 -1
  119. package/dist/dts/internal/utils.d.ts +0 -2
  120. package/dist/dts/internal/utils.d.ts.map +0 -1
  121. package/dist/esm/Logger.js +0 -75
  122. package/dist/esm/Logger.js.map +0 -1
  123. package/dist/esm/Metrics.js +0 -17
  124. package/dist/esm/Metrics.js.map +0 -1
  125. package/dist/esm/NodeSdk.js.map +0 -1
  126. package/dist/esm/Otlp.js +0 -38
  127. package/dist/esm/Otlp.js.map +0 -1
  128. package/dist/esm/OtlpLogger.js +0 -150
  129. package/dist/esm/OtlpLogger.js.map +0 -1
  130. package/dist/esm/OtlpMetrics.js +0 -346
  131. package/dist/esm/OtlpMetrics.js.map +0 -1
  132. package/dist/esm/OtlpResource.js +0 -124
  133. package/dist/esm/OtlpResource.js.map +0 -1
  134. package/dist/esm/OtlpTracer.js +0 -221
  135. package/dist/esm/OtlpTracer.js.map +0 -1
  136. package/dist/esm/Resource.js.map +0 -1
  137. package/dist/esm/Tracer.js +0 -80
  138. package/dist/esm/Tracer.js.map +0 -1
  139. package/dist/esm/WebSdk.js +0 -33
  140. package/dist/esm/WebSdk.js.map +0 -1
  141. package/dist/esm/index.js.map +0 -1
  142. package/dist/esm/internal/metrics.js +0 -278
  143. package/dist/esm/internal/metrics.js.map +0 -1
  144. package/dist/esm/internal/otlpExporter.js +0 -74
  145. package/dist/esm/internal/otlpExporter.js.map +0 -1
  146. package/dist/esm/internal/tracer.js +0 -290
  147. package/dist/esm/internal/tracer.js.map +0 -1
  148. package/dist/esm/internal/utils.js +0 -23
  149. package/dist/esm/internal/utils.js.map +0 -1
  150. package/dist/esm/package.json +0 -4
  151. package/index/package.json +0 -6
  152. package/src/Otlp.ts +0 -66
  153. package/src/OtlpLogger.ts +0 -258
  154. package/src/OtlpMetrics.ts +0 -571
  155. package/src/OtlpResource.ts +0 -232
  156. package/src/OtlpTracer.ts +0 -349
  157. package/src/internal/otlpExporter.ts +0 -124
  158. package/src/internal/tracer.ts +0 -437
  159. package/src/internal/utils.ts +0 -31
  160. /package/dist/{dts/internal → internal}/metrics.d.ts +0 -0
package/src/Tracer.ts CHANGED
@@ -1,133 +1,302 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import type * as Otel from "@opentelemetry/api"
5
- import type { NoSuchElementException } from "effect/Cause"
6
- import type { Tag } from "effect/Context"
7
- import type { Effect } from "effect/Effect"
8
- import type { Layer } from "effect/Layer"
9
- import type { ExternalSpan, ParentSpan, Tracer as EffectTracer } from "effect/Tracer"
10
- import * as internal from "./internal/tracer.js"
11
- import type { Resource } from "./Resource.js"
4
+ import * as Otel from "@opentelemetry/api"
5
+ import * as OtelSemConv from "@opentelemetry/semantic-conventions"
6
+ import * as Cause from "effect/Cause"
7
+ import type * as Clock from "effect/Clock"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Exit from "effect/Exit"
10
+ import { constTrue, dual } from "effect/Function"
11
+ import * as Layer from "effect/Layer"
12
+ import * as Predicate from "effect/Predicate"
13
+ import * as ServiceMap from "effect/ServiceMap"
14
+ import * as Tracer from "effect/Tracer"
15
+ import { recordToAttributes, unknownToAttributeValue } from "./internal/attributes.ts"
16
+ import { Resource } from "./Resource.ts"
12
17
 
13
- /**
14
- * @since 1.0.0
15
- * @category constructors
16
- */
17
- export const make: Effect<EffectTracer, never, OtelTracer> = internal.make
18
+ // =============================================================================
19
+ // Service Definitions
20
+ // =============================================================================
18
21
 
19
22
  /**
20
23
  * @since 1.0.0
21
- * @category constructors
24
+ * @category Services
22
25
  */
23
- export const makeExternalSpan: (
24
- options: {
25
- readonly traceId: string
26
- readonly spanId: string
27
- readonly traceFlags?: number | undefined
28
- readonly traceState?: string | Otel.TraceState | undefined
29
- }
30
- ) => ExternalSpan = internal.makeExternalSpan
26
+ export class OtelTracer extends ServiceMap.Service<
27
+ OtelTracer,
28
+ Otel.Tracer
29
+ >()("@effect/opentelemetry/Tracer") {}
31
30
 
32
31
  /**
33
- * Get the current OpenTelemetry span.
34
- *
35
- * Works with both the official OpenTelemetry API (via `Tracer.layer`, `NodeSdk.layer`, etc.)
36
- * and the lightweight OTLP module (`OtlpTracer.layer`).
37
- *
38
- * When using OTLP, the returned span is a wrapper that conforms to the
39
- * OpenTelemetry `Span` interface.
40
- *
41
32
  * @since 1.0.0
42
- * @category accessors
33
+ * @category Services
43
34
  */
44
- export const currentOtelSpan: Effect<Otel.Span, NoSuchElementException> = internal.currentOtelSpan
35
+ export class OtelTracerProvider extends ServiceMap.Service<
36
+ OtelTracerProvider,
37
+ Otel.TracerProvider
38
+ >()("@effect/opentelemetry/Tracer/OtelTracerProvider") {}
45
39
 
46
40
  /**
47
41
  * @since 1.0.0
48
- * @category layers
42
+ * @category Services
49
43
  */
50
- export const layerWithoutOtelTracer: Layer<never, never, OtelTracer> = internal.layerWithoutOtelTracer
44
+ export class OtelTraceFlags extends ServiceMap.Service<
45
+ OtelTraceFlags,
46
+ Otel.TraceFlags
47
+ >()("@effect/opentelemetry/Tracer/OtelTraceFlags") {}
51
48
 
52
49
  /**
53
50
  * @since 1.0.0
54
- * @category layers
51
+ * @category Services
55
52
  */
56
- export const layer: Layer<OtelTracer, never, Resource | OtelTracerProvider> = internal.layer
53
+ export class OtelTraceState extends ServiceMap.Service<
54
+ OtelTraceState,
55
+ Otel.TraceState
56
+ >()("@effect/opentelemetry/Tracer/OtelTraceState") {}
57
57
 
58
- /**
59
- * @since 1.0.0
60
- * @category layers
61
- */
62
- export const layerGlobal: Layer<OtelTracer, never, Resource> = internal.layerGlobal
58
+ // =============================================================================
59
+ // Constructors
60
+ // =============================================================================
63
61
 
64
62
  /**
65
63
  * @since 1.0.0
66
- * @category layers
64
+ * @category Constructors
67
65
  */
68
- export const layerTracer: Layer<OtelTracer, never, Resource | OtelTracerProvider> = internal.layerTracer
66
+ export const make: Effect.Effect<Tracer.Tracer, never, OtelTracer> = Effect.map(
67
+ Effect.service(OtelTracer),
68
+ (tracer) =>
69
+ Tracer.make({
70
+ span(options) {
71
+ return new OtelSpan(
72
+ Otel.context,
73
+ Otel.trace,
74
+ tracer,
75
+ options
76
+ )
77
+ },
78
+ context(primitive, fiber) {
79
+ const currentSpan = fiber.currentSpan
69
80
 
70
- /**
71
- * @since 1.0.0
72
- * @category layers
73
- */
74
- export const layerGlobalTracer: Layer<OtelTracer, never, Resource> = internal.layerGlobalTracer
81
+ if (currentSpan === undefined) {
82
+ return primitive["~effect/Effect/evaluate"](fiber)
83
+ }
84
+
85
+ return Otel.context.with(
86
+ populateContext(Otel.context.active(), currentSpan),
87
+ () => primitive["~effect/Effect/evaluate"](fiber)
88
+ )
89
+ }
90
+ })
91
+ )
75
92
 
76
93
  /**
77
94
  * @since 1.0.0
78
- * @category tags
95
+ * @category Constructors
79
96
  */
80
- export interface OtelTracerProvider {
81
- readonly _: unique symbol
97
+ export const makeExternalSpan = (options: {
98
+ readonly traceId: string
99
+ readonly spanId: string
100
+ readonly traceFlags?: number | undefined
101
+ readonly traceState?: string | Otel.TraceState | undefined
102
+ }): Tracer.ExternalSpan => {
103
+ let annotations = ServiceMap.empty()
104
+
105
+ if (options.traceFlags !== undefined) {
106
+ annotations = ServiceMap.add(annotations, OtelTraceFlags, options.traceFlags)
107
+ }
108
+
109
+ if (typeof options.traceState === "string") {
110
+ try {
111
+ const traceState = Otel.createTraceState(options.traceState)
112
+ annotations = ServiceMap.add(annotations, OtelTraceState, traceState)
113
+ } catch {
114
+ //
115
+ }
116
+ } else if (options.traceState) {
117
+ annotations = ServiceMap.add(annotations, OtelTraceState, options.traceState)
118
+ }
119
+
120
+ return {
121
+ _tag: "ExternalSpan",
122
+ traceId: options.traceId,
123
+ spanId: options.spanId,
124
+ sampled: Predicate.isNotUndefined(options.traceFlags) ? isSampled(options.traceFlags) : true,
125
+ annotations
126
+ }
82
127
  }
83
128
 
129
+ // =============================================================================
130
+ // Layers
131
+ // =============================================================================
132
+
84
133
  /**
85
134
  * @since 1.0.0
86
- * @category tags
135
+ * @category Layers
87
136
  */
88
- export const OtelTracerProvider: Tag<OtelTracerProvider, Otel.TracerProvider> = internal.TracerProvider
137
+ export const layerGlobalProvider: Layer.Layer<OtelTracerProvider> = Layer.sync(
138
+ OtelTracerProvider,
139
+ () => Otel.trace.getTracerProvider()
140
+ )
89
141
 
90
142
  /**
91
143
  * @since 1.0.0
92
- * @category tags
144
+ * @category Layers
93
145
  */
94
- export interface OtelTracer {
95
- readonly _: unique symbol
96
- }
146
+ export const layerTracer: Layer.Layer<OtelTracer, never, OtelTracerProvider | Resource> = Layer.effect(
147
+ OtelTracer,
148
+ Effect.gen(function*() {
149
+ const resource = yield* Resource
150
+ const provider = yield* OtelTracerProvider
151
+ return provider.getTracer(
152
+ resource.attributes[OtelSemConv.ATTR_SERVICE_NAME] as string,
153
+ resource.attributes[OtelSemConv.ATTR_SERVICE_VERSION] as string
154
+ )
155
+ })
156
+ )
97
157
 
98
158
  /**
99
159
  * @since 1.0.0
100
- * @category tags
160
+ * @category Layers
101
161
  */
102
- export const OtelTracer: Tag<OtelTracer, Otel.Tracer> = internal.Tracer
162
+ export const layerGlobalTracer: Layer.Layer<OtelTracer, never, Resource> = layerTracer.pipe(
163
+ Layer.provide(layerGlobalProvider)
164
+ )
103
165
 
104
166
  /**
105
167
  * @since 1.0.0
106
- * @category tags
168
+ * @category Layers
107
169
  */
108
- export interface OtelTraceFlags {
109
- readonly _: unique symbol
110
- }
170
+ export const layerGlobal: Layer.Layer<OtelTracer, never, Resource> = Layer.effect(Tracer.Tracer, make).pipe(
171
+ Layer.provideMerge(layerGlobalTracer)
172
+ )
111
173
 
112
174
  /**
113
175
  * @since 1.0.0
114
- * @category tags
176
+ * @category Layers
115
177
  */
116
- export const OtelTraceFlags: Tag<OtelTraceFlags, Otel.TraceFlags> = internal.traceFlagsTag
178
+ export const layerWithoutOtelTracer: Layer.Layer<never, never, OtelTracer> = Layer.effect(Tracer.Tracer, make)
117
179
 
118
180
  /**
119
181
  * @since 1.0.0
120
- * @category tags
182
+ * @category Layers
121
183
  */
122
- export interface OtelTraceState {
123
- readonly _: unique symbol
124
- }
184
+ export const layer: Layer.Layer<OtelTracer, never, OtelTracerProvider | Resource> = layerWithoutOtelTracer.pipe(
185
+ Layer.provideMerge(layerTracer)
186
+ )
187
+
188
+ // =============================================================================
189
+ // Utilities / Combinators
190
+ // =============================================================================
191
+
192
+ const bigint1e6 = BigInt(1_000_000)
193
+ const bigint1e9 = BigInt(1_000_000_000)
125
194
 
126
195
  /**
196
+ * Get the current OpenTelemetry span.
197
+ *
198
+ * Works with both the official OpenTelemetry API (via `Tracer.layer`,
199
+ * `NodeSdk.layer`, etc.) and the lightweight OTLP module (`OtlpTracer.layer`).
200
+ *
201
+ * When using OTLP, the returned span is a wrapper that conforms to the
202
+ * OpenTelemetry `Span` interface.
203
+ *
127
204
  * @since 1.0.0
128
- * @category tags
205
+ * @category accessors
129
206
  */
130
- export const OtelTraceState: Tag<OtelTraceState, Otel.TraceState> = internal.traceStateTag
207
+ export const currentOtelSpan: Effect.Effect<Otel.Span, Cause.NoSuchElementError> = Effect.clockWith((clock) =>
208
+ Effect.map(Effect.currentSpan, (span) =>
209
+ OtelSpanTypeId in span
210
+ ? (span as OtelSpan).span
211
+ : makeOtelSpan(span, clock))
212
+ )
213
+
214
+ const makeOtelSpan = (span: Tracer.Span, clock: Clock.Clock): Otel.Span => {
215
+ const spanContext: Otel.SpanContext = {
216
+ traceId: span.traceId,
217
+ spanId: span.spanId,
218
+ traceFlags: span.sampled ? Otel.TraceFlags.SAMPLED : Otel.TraceFlags.NONE,
219
+ isRemote: false
220
+ }
221
+
222
+ let exit = Exit.void
223
+
224
+ const self: Otel.Span = {
225
+ spanContext: () => spanContext,
226
+ setAttribute(key, value) {
227
+ span.attribute(key, value)
228
+ return self
229
+ },
230
+ setAttributes(attributes) {
231
+ for (const [key, value] of Object.entries(attributes)) {
232
+ span.attribute(key, value)
233
+ }
234
+ return self
235
+ },
236
+ addEvent(name) {
237
+ let attributes: Otel.Attributes | undefined = undefined
238
+ let startTime: Otel.TimeInput | undefined = undefined
239
+ if (arguments.length === 3) {
240
+ attributes = arguments[1]
241
+ startTime = arguments[2]
242
+ } else {
243
+ startTime = arguments[1]
244
+ }
245
+ span.event(name, convertOtelTimeInput(startTime, clock), attributes)
246
+ return self
247
+ },
248
+ addLink(link) {
249
+ span.addLinks([{
250
+ span: makeExternalSpan(link.context),
251
+ attributes: link.attributes ?? {}
252
+ }])
253
+ return self
254
+ },
255
+ addLinks(links) {
256
+ span.addLinks(links.map((link) => ({
257
+ span: makeExternalSpan(link.context),
258
+ attributes: link.attributes ?? {}
259
+ })))
260
+ return self
261
+ },
262
+ setStatus(status) {
263
+ exit = Otel.SpanStatusCode.ERROR
264
+ ? Exit.die(status.message ?? "Unknown error")
265
+ : Exit.void
266
+ return self
267
+ },
268
+ updateName: () => self,
269
+ end(endTime) {
270
+ const time = convertOtelTimeInput(endTime, clock)
271
+ span.end(time, exit)
272
+ return self
273
+ },
274
+ isRecording: constTrue,
275
+ recordException(exception, timeInput) {
276
+ const time = convertOtelTimeInput(timeInput, clock)
277
+ const cause = Cause.fail(exception)
278
+ const error = Cause.prettyErrors(cause)[0]
279
+ span.event(error.message, time, {
280
+ "exception.type": error.name,
281
+ "exception.message": error.message,
282
+ "exception.stacktrace": error.stack ?? ""
283
+ })
284
+ }
285
+ }
286
+ return self
287
+ }
288
+
289
+ const convertOtelTimeInput = (input: Otel.TimeInput | undefined, clock: Clock.Clock): bigint => {
290
+ if (input === undefined) {
291
+ return clock.currentTimeNanosUnsafe()
292
+ } else if (typeof input === "number") {
293
+ return BigInt(Math.round(input * 1_000_000))
294
+ } else if (input instanceof Date) {
295
+ return BigInt(input.getTime()) * bigint1e6
296
+ }
297
+ const [seconds, nanos] = input
298
+ return BigInt(seconds) * bigint1e9 + BigInt(nanos)
299
+ }
131
300
 
132
301
  /**
133
302
  * Set the effect's parent span from the given opentelemetry `SpanContext`.
@@ -136,7 +305,7 @@ export const OtelTraceState: Tag<OtelTraceState, Otel.TraceState> = internal.tra
136
305
  * attach to a parent span.
137
306
  *
138
307
  * @since 1.0.0
139
- * @category propagation
308
+ * @category Propagation
140
309
  */
141
310
  export const withSpanContext: {
142
311
  /**
@@ -146,11 +315,11 @@ export const withSpanContext: {
146
315
  * attach to a parent span.
147
316
  *
148
317
  * @since 1.0.0
149
- * @category propagation
318
+ * @category Propagation
150
319
  */
151
320
  (spanContext: Otel.SpanContext): <A, E, R>(
152
- effect: Effect<A, E, R>
153
- ) => Effect<A, E, Exclude<R, ParentSpan>>
321
+ self: Effect.Effect<A, E, R>
322
+ ) => Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
154
323
  /**
155
324
  * Set the effect's parent span from the given opentelemetry `SpanContext`.
156
325
  *
@@ -158,7 +327,229 @@ export const withSpanContext: {
158
327
  * attach to a parent span.
159
328
  *
160
329
  * @since 1.0.0
161
- * @category propagation
330
+ * @category Propagation
162
331
  */
163
- <A, E, R>(effect: Effect<A, E, R>, spanContext: Otel.SpanContext): Effect<A, E, Exclude<R, ParentSpan>>
164
- } = internal.withSpanContext
332
+ <A, E, R>(self: Effect.Effect<A, E, R>, spanContext: Otel.SpanContext): Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
333
+ } = dual(2, <A, E, R>(
334
+ self: Effect.Effect<A, E, R>,
335
+ spanContext: Otel.SpanContext
336
+ ) => Effect.withParentSpan(self, makeExternalSpan(spanContext)))
337
+
338
+ // =============================================================================
339
+ // Internals
340
+ // =============================================================================
341
+
342
+ const OtelSpanTypeId = "~@effect/opentelemetry/Tracer/OtelSpan"
343
+
344
+ const kindMap = {
345
+ "internal": Otel.SpanKind.INTERNAL,
346
+ "client": Otel.SpanKind.CLIENT,
347
+ "server": Otel.SpanKind.SERVER,
348
+ "producer": Otel.SpanKind.PRODUCER,
349
+ "consumer": Otel.SpanKind.CONSUMER
350
+ }
351
+
352
+ /** @internal */
353
+ export class OtelSpan implements Tracer.Span {
354
+ readonly [OtelSpanTypeId]: typeof OtelSpanTypeId
355
+ readonly _tag = "Span"
356
+
357
+ readonly name: string
358
+ readonly kind: Tracer.SpanKind
359
+ readonly annotations: ServiceMap.ServiceMap<never>
360
+ readonly links: Array<Tracer.SpanLink>
361
+ readonly span: Otel.Span
362
+ readonly spanId: string
363
+ readonly traceId: string
364
+ readonly attributes = new Map<string, unknown>()
365
+ readonly sampled: boolean
366
+ readonly parent: Tracer.AnySpan | undefined
367
+ status: Tracer.SpanStatus
368
+
369
+ constructor(
370
+ contextApi: Otel.ContextAPI,
371
+ traceApi: Otel.TraceAPI,
372
+ tracer: Otel.Tracer,
373
+ options: Parameters<Tracer.Tracer["span"]>[0]
374
+ ) {
375
+ this[OtelSpanTypeId] = OtelSpanTypeId
376
+ this.name = options.name
377
+ this.annotations = options.annotations
378
+ this.links = options.links
379
+ this.kind = options.kind
380
+ const active = contextApi.active()
381
+ this.parent = options.parent ?? (options?.root !== true) ?
382
+ getOtelParent(traceApi, active, options.annotations) :
383
+ undefined
384
+ this.span = tracer.startSpan(
385
+ options.name,
386
+ {
387
+ startTime: nanosToHrTime(options.startTime),
388
+ links: options.links.length > 0
389
+ ? options.links.map((link) => ({
390
+ context: makeSpanContext(link.span),
391
+ attributes: recordToAttributes(link.attributes)
392
+ }))
393
+ : undefined as any,
394
+ kind: kindMap[this.kind]
395
+ },
396
+ this.parent ? populateContext(active, this.parent, options.annotations) : Otel.trace.deleteSpan(active)
397
+ )
398
+ const spanContext = this.span.spanContext()
399
+ this.spanId = spanContext.spanId
400
+ this.traceId = spanContext.traceId
401
+ this.status = {
402
+ _tag: "Started",
403
+ startTime: options.startTime
404
+ }
405
+ this.sampled = isSampled(spanContext.traceFlags)
406
+ }
407
+
408
+ attribute(key: string, value: unknown) {
409
+ this.span.setAttribute(key, unknownToAttributeValue(value))
410
+ this.attributes.set(key, value)
411
+ }
412
+
413
+ addLinks(links: ReadonlyArray<Tracer.SpanLink>): void {
414
+ // oxlint-disable-next-line no-restricted-syntax
415
+ this.links.push(...links)
416
+ this.span.addLinks(links.map((link) => ({
417
+ context: makeSpanContext(link.span),
418
+ attributes: recordToAttributes(link.attributes)
419
+ })))
420
+ }
421
+
422
+ end(endTime: bigint, exit: Exit.Exit<unknown, unknown>) {
423
+ const hrTime = nanosToHrTime(endTime)
424
+ this.status = {
425
+ _tag: "Ended",
426
+ endTime,
427
+ exit,
428
+ startTime: this.status.startTime
429
+ }
430
+
431
+ if (exit._tag === "Success") {
432
+ this.span.setStatus({ code: Otel.SpanStatusCode.OK })
433
+ } else {
434
+ if (Cause.hasInterruptsOnly(exit.cause)) {
435
+ this.span.setStatus({
436
+ code: Otel.SpanStatusCode.OK,
437
+ message: Cause.pretty(exit.cause)
438
+ })
439
+ this.span.setAttribute("span.label", "⚠︎ Interrupted")
440
+ this.span.setAttribute("status.interrupted", true)
441
+ } else {
442
+ const errors = Cause.prettyErrors(exit.cause)
443
+ if (errors.length > 0) {
444
+ for (const error of errors) {
445
+ this.span.recordException(error, hrTime)
446
+ }
447
+ this.span.setStatus({
448
+ code: Otel.SpanStatusCode.ERROR,
449
+ message: errors[0].message
450
+ })
451
+ } else {
452
+ // empty cause means no error
453
+ this.span.setStatus({ code: Otel.SpanStatusCode.OK })
454
+ }
455
+ }
456
+ }
457
+ this.span.end(hrTime)
458
+ }
459
+
460
+ event(name: string, startTime: bigint, attributes?: Record<string, unknown>) {
461
+ this.span.addEvent(
462
+ name,
463
+ attributes ? recordToAttributes(attributes) : undefined,
464
+ nanosToHrTime(startTime)
465
+ )
466
+ }
467
+ }
468
+
469
+ const isSampled = (traceFlags: Otel.TraceFlags): boolean =>
470
+ (traceFlags & Otel.TraceFlags.SAMPLED) === Otel.TraceFlags.SAMPLED
471
+
472
+ const nanosToHrTime = (timestamp: bigint): Otel.HrTime => {
473
+ return [Number(timestamp / bigint1e9), Number(timestamp % bigint1e9)]
474
+ }
475
+
476
+ const getOtelParent = (
477
+ tracer: Otel.TraceAPI,
478
+ context: Otel.Context,
479
+ annotations: ServiceMap.ServiceMap<never>
480
+ ): Tracer.AnySpan | undefined => {
481
+ const active = tracer.getSpan(context)
482
+ const otelParent = active ? active.spanContext() : undefined
483
+ return otelParent
484
+ ? Tracer.externalSpan({
485
+ spanId: otelParent.spanId,
486
+ traceId: otelParent.traceId,
487
+ sampled: (otelParent.traceFlags & 1) === 1,
488
+ annotations
489
+ })
490
+ : undefined
491
+ }
492
+
493
+ const makeSpanContext = (
494
+ span: Tracer.AnySpan,
495
+ annotations?: ServiceMap.ServiceMap<never>
496
+ ): Otel.SpanContext => {
497
+ const traceFlags = makeTraceFlags(span, annotations)
498
+ const traceState = makeTraceState(span, annotations)!
499
+ return ({
500
+ spanId: span.spanId,
501
+ traceId: span.traceId,
502
+ isRemote: span._tag === "ExternalSpan",
503
+ traceFlags,
504
+ traceState
505
+ })
506
+ }
507
+
508
+ const makeTraceFlags = (
509
+ span: Tracer.AnySpan,
510
+ annotations: ServiceMap.ServiceMap<never> | undefined
511
+ ): Otel.TraceFlags => {
512
+ let traceFlags: Otel.TraceFlags | undefined
513
+ if (Predicate.isNotUndefined(annotations)) {
514
+ traceFlags = extractTraceService(span, annotations, OtelTraceFlags)
515
+ if (Predicate.isUndefined(traceFlags)) {
516
+ traceFlags = ServiceMap.getOrUndefined(span.annotations, OtelTraceFlags)
517
+ }
518
+ }
519
+ return traceFlags ?? Otel.TraceFlags.SAMPLED
520
+ }
521
+
522
+ const makeTraceState = (
523
+ span: Tracer.AnySpan,
524
+ annotations: ServiceMap.ServiceMap<never> | undefined
525
+ ): Otel.TraceState | undefined => {
526
+ let traceState: Otel.TraceState | undefined
527
+ if (Predicate.isNotUndefined(annotations)) {
528
+ traceState = extractTraceService(span, annotations, OtelTraceState)
529
+ if (Predicate.isUndefined(traceState)) {
530
+ traceState = ServiceMap.getOrUndefined(span.annotations, OtelTraceState)
531
+ }
532
+ }
533
+ return traceState
534
+ }
535
+
536
+ const extractTraceService = <I, S>(
537
+ parent: Tracer.AnySpan,
538
+ annotations: ServiceMap.ServiceMap<never>,
539
+ service: ServiceMap.Service<I, S>
540
+ ) => {
541
+ const instance = ServiceMap.getOrUndefined(annotations, service)
542
+ if (Predicate.isNotUndefined(instance)) {
543
+ return instance
544
+ }
545
+ return ServiceMap.getOrUndefined(parent.annotations, service)
546
+ }
547
+
548
+ const populateContext = (
549
+ context: Otel.Context,
550
+ span: Tracer.AnySpan,
551
+ annotations?: ServiceMap.ServiceMap<never> | undefined
552
+ ): Otel.Context =>
553
+ span instanceof OtelSpan ?
554
+ Otel.trace.setSpan(context, span.span) :
555
+ Otel.trace.setSpanContext(context, makeSpanContext(span, annotations))