@uploadista/observability 0.0.18-beta.8 → 0.0.18

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.
@@ -1,9 +1,18 @@
1
1
  import { NodeSdk, WebSdk } from "@effect/opentelemetry";
2
+ import { trace } from "@opentelemetry/api";
2
3
  import {
3
4
  BatchSpanProcessor,
4
5
  ConsoleSpanExporter,
6
+ type SpanProcessor,
5
7
  } from "@opentelemetry/sdk-trace-base";
6
- import { Context, Effect, Layer } from "effect";
8
+ import { Context, Effect, Layer, Option, Tracer } from "effect";
9
+ import {
10
+ createOtlpTraceExporter,
11
+ getServiceName,
12
+ isOtlpExportEnabled,
13
+ parseResourceAttributes,
14
+ } from "./exporters.js";
15
+ import type { TraceContext } from "./types.js";
7
16
 
8
17
  // ============================================================================
9
18
  // Universal Tracing (Environment-agnostic)
@@ -65,3 +74,410 @@ export const WorkersSdkLive = WebSdk.layer(() => ({
65
74
  // Export span data to the console in Workers environment
66
75
  spanProcessor: new BatchSpanProcessor(new ConsoleSpanExporter()),
67
76
  }));
77
+
78
+ // ============================================================================
79
+ // OTLP Export Layers (Production)
80
+ // ============================================================================
81
+
82
+ /**
83
+ * Configuration options for OTLP SDK layers.
84
+ */
85
+ export interface OtlpSdkConfig {
86
+ /** Service name for traces. Defaults to OTEL_SERVICE_NAME or "uploadista" */
87
+ serviceName?: string;
88
+ /** Additional resource attributes to include in all spans */
89
+ resourceAttributes?: Record<string, string>;
90
+ /** Maximum queue size for batch processor. Defaults to 512 */
91
+ maxQueueSize?: number;
92
+ /** Maximum export batch size. Defaults to 512 */
93
+ maxExportBatchSize?: number;
94
+ /** Schedule delay in milliseconds. Defaults to 5000 */
95
+ scheduledDelayMillis?: number;
96
+ /** Export timeout in milliseconds. Defaults to 5000 */
97
+ exportTimeoutMillis?: number;
98
+ }
99
+
100
+ /**
101
+ * Creates a BatchSpanProcessor with OTLP exporter and graceful degradation.
102
+ *
103
+ * The processor is configured with:
104
+ * - Configurable queue limits to prevent memory issues
105
+ * - Export timeouts to prevent blocking
106
+ * - Error handling that drops data rather than failing requests
107
+ *
108
+ * @param config - Optional configuration
109
+ * @returns Configured BatchSpanProcessor
110
+ */
111
+ function createOtlpSpanProcessor(config: OtlpSdkConfig = {}): SpanProcessor {
112
+ const exporter = createOtlpTraceExporter();
113
+
114
+ return new BatchSpanProcessor(exporter, {
115
+ maxQueueSize: config.maxQueueSize ?? 512,
116
+ maxExportBatchSize: config.maxExportBatchSize ?? 512,
117
+ scheduledDelayMillis: config.scheduledDelayMillis ?? 5000,
118
+ // Default to 30 seconds to accommodate cloud endpoints like Grafana Cloud
119
+ exportTimeoutMillis: config.exportTimeoutMillis ?? 30000,
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Creates resource configuration from environment and config.
125
+ */
126
+ function createResourceConfig(config: OtlpSdkConfig = {}): {
127
+ serviceName: string;
128
+ [key: string]: string;
129
+ } {
130
+ const serviceName = config.serviceName ?? getServiceName("uploadista");
131
+ const envAttributes = parseResourceAttributes();
132
+ const configAttributes = config.resourceAttributes ?? {};
133
+
134
+ return {
135
+ serviceName,
136
+ ...envAttributes,
137
+ ...configAttributes,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Node.js OTLP SDK Layer for production use.
143
+ *
144
+ * Exports traces to an OTLP-compatible endpoint configured via environment variables:
145
+ * - OTEL_EXPORTER_OTLP_ENDPOINT: Base endpoint (default: http://localhost:4318)
146
+ * - OTEL_EXPORTER_OTLP_HEADERS: Authentication headers
147
+ * - OTEL_SERVICE_NAME: Service name (default: uploadista)
148
+ * - OTEL_RESOURCE_ATTRIBUTES: Additional resource attributes
149
+ * - UPLOADISTA_OBSERVABILITY_ENABLED: Set to "false" to disable (default: true)
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * import { OtlpNodeSdkLive } from "@uploadista/observability";
154
+ * import { Effect } from "effect";
155
+ *
156
+ * // With default environment configuration
157
+ * const program = myEffect.pipe(Effect.provide(OtlpNodeSdkLive));
158
+ *
159
+ * // Run with:
160
+ * // OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
161
+ * // OTEL_SERVICE_NAME=my-upload-service
162
+ * ```
163
+ */
164
+ export const OtlpNodeSdkLive = NodeSdk.layer(() => {
165
+ // Check if observability is disabled
166
+ if (!isOtlpExportEnabled()) {
167
+ // Return no-op configuration (no span processor means no export)
168
+ return {
169
+ resource: createResourceConfig(),
170
+ };
171
+ }
172
+
173
+ return {
174
+ resource: createResourceConfig(),
175
+ spanProcessor: createOtlpSpanProcessor(),
176
+ };
177
+ });
178
+
179
+ /**
180
+ * Creates a customized OTLP Node.js SDK Layer.
181
+ *
182
+ * Use this when you need to customize the SDK configuration beyond
183
+ * what environment variables provide.
184
+ *
185
+ * @param config - Custom configuration options
186
+ * @returns Configured Effect Layer
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * const customSdk = createOtlpNodeSdkLayer({
191
+ * serviceName: "my-custom-service",
192
+ * resourceAttributes: {
193
+ * "tenant.id": "abc123",
194
+ * "deployment.environment": "production"
195
+ * },
196
+ * maxQueueSize: 1024,
197
+ * });
198
+ *
199
+ * const program = myEffect.pipe(Effect.provide(customSdk));
200
+ * ```
201
+ */
202
+ export function createOtlpNodeSdkLayer(config: OtlpSdkConfig = {}) {
203
+ return NodeSdk.layer(() => {
204
+ if (!isOtlpExportEnabled()) {
205
+ return {
206
+ resource: createResourceConfig(config),
207
+ };
208
+ }
209
+
210
+ return {
211
+ resource: createResourceConfig(config),
212
+ spanProcessor: createOtlpSpanProcessor(config),
213
+ };
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Browser OTLP SDK Layer for production use.
219
+ *
220
+ * Similar to OtlpNodeSdkLive but uses fetch API for browser compatibility.
221
+ * Note: Browser environments may have CORS restrictions.
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { OtlpWebSdkLive } from "@uploadista/observability";
226
+ *
227
+ * const program = myEffect.pipe(Effect.provide(OtlpWebSdkLive));
228
+ * ```
229
+ */
230
+ export const OtlpWebSdkLive = WebSdk.layer(() => {
231
+ if (!isOtlpExportEnabled()) {
232
+ return {
233
+ resource: createResourceConfig(),
234
+ };
235
+ }
236
+
237
+ return {
238
+ resource: createResourceConfig(),
239
+ spanProcessor: createOtlpSpanProcessor(),
240
+ };
241
+ });
242
+
243
+ /**
244
+ * Creates a customized OTLP Web SDK Layer.
245
+ *
246
+ * @param config - Custom configuration options
247
+ * @returns Configured Effect Layer for browser environments
248
+ */
249
+ export function createOtlpWebSdkLayer(config: OtlpSdkConfig = {}) {
250
+ return WebSdk.layer(() => {
251
+ if (!isOtlpExportEnabled()) {
252
+ return {
253
+ resource: createResourceConfig(config),
254
+ };
255
+ }
256
+
257
+ return {
258
+ resource: createResourceConfig(config),
259
+ spanProcessor: createOtlpSpanProcessor(config),
260
+ };
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Cloudflare Workers OTLP SDK Layer for production use.
266
+ *
267
+ * Uses the Web SDK under the hood with fetch-based export.
268
+ * Suitable for edge computing environments.
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * import { OtlpWorkersSdkLive } from "@uploadista/observability";
273
+ *
274
+ * export default {
275
+ * async fetch(request, env) {
276
+ * const program = handleRequest(request).pipe(
277
+ * Effect.provide(OtlpWorkersSdkLive)
278
+ * );
279
+ * return Effect.runPromise(program);
280
+ * }
281
+ * };
282
+ * ```
283
+ */
284
+ export const OtlpWorkersSdkLive = WebSdk.layer(() => {
285
+ const config: OtlpSdkConfig = {
286
+ serviceName: getServiceName("uploadista-workers"),
287
+ };
288
+
289
+ if (!isOtlpExportEnabled()) {
290
+ return {
291
+ resource: createResourceConfig(config),
292
+ };
293
+ }
294
+
295
+ return {
296
+ resource: createResourceConfig(config),
297
+ spanProcessor: createOtlpSpanProcessor(config),
298
+ };
299
+ });
300
+
301
+ /**
302
+ * Creates a customized OTLP Workers SDK Layer.
303
+ *
304
+ * @param config - Custom configuration options
305
+ * @returns Configured Effect Layer for Cloudflare Workers
306
+ */
307
+ export function createOtlpWorkersSdkLayer(config: OtlpSdkConfig = {}) {
308
+ return WebSdk.layer(() => {
309
+ const effectiveConfig = {
310
+ serviceName: getServiceName("uploadista-workers"),
311
+ ...config,
312
+ };
313
+
314
+ if (!isOtlpExportEnabled()) {
315
+ return {
316
+ resource: createResourceConfig(effectiveConfig),
317
+ };
318
+ }
319
+
320
+ return {
321
+ resource: createResourceConfig(effectiveConfig),
322
+ spanProcessor: createOtlpSpanProcessor(effectiveConfig),
323
+ };
324
+ });
325
+ }
326
+
327
+ // ============================================================================
328
+ // Distributed Tracing Context Utilities
329
+ // ============================================================================
330
+
331
+ /**
332
+ * @deprecated Use `captureTraceContextEffect` instead. This synchronous function
333
+ * uses OpenTelemetry's `trace.getActiveSpan()` which may not be synchronized
334
+ * with Effect's span context when using @effect/opentelemetry.
335
+ *
336
+ * @returns TraceContext if there's an active OpenTelemetry span, undefined otherwise
337
+ */
338
+ export function captureTraceContext(): TraceContext | undefined {
339
+ const currentSpan = trace.getActiveSpan();
340
+ if (!currentSpan) {
341
+ return undefined;
342
+ }
343
+
344
+ const spanContext = currentSpan.spanContext();
345
+ return {
346
+ traceId: spanContext.traceId,
347
+ spanId: spanContext.spanId,
348
+ traceFlags: spanContext.traceFlags,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Captures the current Effect trace context for distributed tracing.
354
+ *
355
+ * Uses Effect's `currentSpan` to get the active span, which is more reliable
356
+ * than OpenTelemetry's `trace.getActiveSpan()` when using @effect/opentelemetry
357
+ * because Effect manages its own span context that may not be synchronized
358
+ * with OpenTelemetry's global context.
359
+ *
360
+ * Use this to save the trace context (traceId, spanId, traceFlags) for later
361
+ * use in distributed tracing. The captured context can be stored alongside
362
+ * data (e.g., in KV store with upload metadata) and restored later using
363
+ * `createExternalSpan` and passing it to `Effect.withSpan`'s `parent` option.
364
+ *
365
+ * @returns Effect yielding TraceContext if there's an active span, undefined otherwise
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * // Capture context during upload creation
370
+ * const createUpload = Effect.gen(function* () {
371
+ * const traceContext = yield* captureTraceContextEffect;
372
+ *
373
+ * const file: UploadFile = {
374
+ * id: uploadId,
375
+ * traceContext, // Store for later
376
+ * // ...
377
+ * };
378
+ * yield* kvStore.set(uploadId, file);
379
+ * }).pipe(Effect.withSpan("upload-create", { ... }));
380
+ * ```
381
+ */
382
+ export const captureTraceContextEffect: Effect.Effect<
383
+ TraceContext | undefined
384
+ > = Effect.gen(function* () {
385
+ const spanOption = yield* Effect.currentSpan.pipe(Effect.option);
386
+ return Option.match(spanOption, {
387
+ onNone: () => undefined,
388
+ onSome: (span) => ({
389
+ traceId: span.traceId,
390
+ spanId: span.spanId,
391
+ traceFlags: span.sampled ? 1 : 0,
392
+ }),
393
+ });
394
+ });
395
+
396
+ /**
397
+ * Creates an ExternalSpan from a stored trace context.
398
+ *
399
+ * Use this to create a parent span reference that can be passed to
400
+ * `Effect.withSpan`'s `parent` option for distributed tracing.
401
+ *
402
+ * **Important:** The parent must be passed directly to `Effect.withSpan`'s
403
+ * options, not provided as a service afterward.
404
+ *
405
+ * @param traceContext - Previously captured trace context
406
+ * @returns ExternalSpan that can be used as a parent in Effect.withSpan
407
+ *
408
+ * @example
409
+ * ```typescript
410
+ * // Create parent span from stored trace context
411
+ * const parentSpan = file.traceContext
412
+ * ? createExternalSpan(file.traceContext)
413
+ * : undefined;
414
+ *
415
+ * // Pass parent directly to withSpan
416
+ * const chunkEffect = Effect.gen(function* () {
417
+ * // ... chunk upload logic
418
+ * }).pipe(
419
+ * Effect.withSpan("upload-chunk", {
420
+ * attributes: { ... },
421
+ * parent: parentSpan, // Link to original trace
422
+ * })
423
+ * );
424
+ * ```
425
+ */
426
+ export function createExternalSpan(traceContext: TraceContext) {
427
+ return Tracer.externalSpan({
428
+ traceId: traceContext.traceId,
429
+ spanId: traceContext.spanId,
430
+ sampled: traceContext.traceFlags === 1,
431
+ });
432
+ }
433
+
434
+ /**
435
+ * @deprecated Use `createExternalSpan` instead and pass the result to
436
+ * `Effect.withSpan`'s `parent` option directly. This function doesn't
437
+ * work correctly because Effect.withSpan reads the parent at construction
438
+ * time, not from the provided service.
439
+ *
440
+ * @example
441
+ * ```typescript
442
+ * // Instead of:
443
+ * withParentContext(traceContext)(effect.pipe(Effect.withSpan(...)))
444
+ *
445
+ * // Do this:
446
+ * const parent = createExternalSpan(traceContext);
447
+ * effect.pipe(Effect.withSpan("name", { parent }))
448
+ * ```
449
+ */
450
+ export function withParentContext(traceContext: TraceContext) {
451
+ return <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => {
452
+ const externalSpan = Tracer.externalSpan({
453
+ traceId: traceContext.traceId,
454
+ spanId: traceContext.spanId,
455
+ sampled: traceContext.traceFlags === 1,
456
+ });
457
+
458
+ return effect.pipe(Effect.provideService(Tracer.ParentSpan, externalSpan));
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Checks if there's an active trace context.
464
+ *
465
+ * Useful for conditional logic based on whether tracing is active.
466
+ *
467
+ * @returns true if there's an active span with valid trace context
468
+ *
469
+ * @example
470
+ * ```typescript
471
+ * if (hasActiveTraceContext()) {
472
+ * console.log("Tracing is active");
473
+ * }
474
+ * ```
475
+ */
476
+ export function hasActiveTraceContext(): boolean {
477
+ const span = trace.getActiveSpan();
478
+ if (!span) return false;
479
+
480
+ const ctx = span.spanContext();
481
+ // Check if the trace ID is valid (not all zeros)
482
+ return ctx.traceId !== "00000000000000000000000000000000";
483
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OpenTelemetry trace context types for distributed tracing.
3
+ *
4
+ * These types enable trace context propagation across HTTP requests,
5
+ * allowing spans from multiple requests to be grouped under a single trace.
6
+ *
7
+ * @module observability/core/types
8
+ */
9
+
10
+ /**
11
+ * Trace context for distributed tracing.
12
+ *
13
+ * This structure holds the essential OpenTelemetry trace context that needs
14
+ * to be persisted and propagated across requests to maintain trace continuity.
15
+ *
16
+ * @property traceId - 128-bit unique identifier for the entire trace (32 hex chars)
17
+ * @property spanId - 64-bit unique identifier for the parent span (16 hex chars)
18
+ * @property traceFlags - Sampling decision (1 = sampled, 0 = not sampled)
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Store trace context with upload metadata
23
+ * const traceContext: TraceContext = {
24
+ * traceId: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
25
+ * spanId: "a1b2c3d4e5f6a1b2",
26
+ * traceFlags: 1
27
+ * };
28
+ *
29
+ * // Later, restore context to link spans
30
+ * if (uploadFile.traceContext) {
31
+ * yield* withParentContext(uploadFile.traceContext)(
32
+ * Effect.withSpan("upload-chunk", { ... })(chunkEffect)
33
+ * );
34
+ * }
35
+ * ```
36
+ */
37
+ export type TraceContext = {
38
+ /** 128-bit trace identifier (32 hex characters) */
39
+ traceId: string;
40
+ /** 64-bit span identifier (16 hex characters) */
41
+ spanId: string;
42
+ /** Trace flags (1 = sampled) */
43
+ traceFlags: number;
44
+ };
@@ -66,13 +66,20 @@ export const withFlowDuration = <A, E, R>(
66
66
 
67
67
  /**
68
68
  * Helper to track node duration
69
+ * @param nodeId - Unique node identifier
70
+ * @param nodeType - Generic node type (e.g., "optimize", "resize")
71
+ * @param effect - The effect to track
72
+ * @param nodeTypeId - Optional specific node type ID (e.g., "optimize-image", "resize-video")
69
73
  */
70
74
  export const withNodeDuration = <A, E, R>(
71
75
  nodeId: string,
72
76
  nodeType: string,
73
77
  effect: Effect.Effect<A, E, R>,
78
+ nodeTypeId?: string,
74
79
  ): Effect.Effect<A, E, R> => {
75
80
  const metrics = createFlowMetrics();
81
+ // Use nodeTypeId for span name if available, fallback to nodeType
82
+ const spanName = nodeTypeId ?? nodeType;
76
83
  return Effect.gen(function* () {
77
84
  const startTime = Date.now();
78
85
  const result = yield* effect;
@@ -81,10 +88,11 @@ export const withNodeDuration = <A, E, R>(
81
88
  yield* Metric.update(metrics.nodeLatencySummary, duration);
82
89
  return result;
83
90
  }).pipe(
84
- Effect.withSpan(`node-${nodeType}`, {
91
+ Effect.withSpan(`node-${spanName}`, {
85
92
  attributes: {
86
93
  "node.id": nodeId,
87
94
  "node.type": nodeType,
95
+ "node.type_id": nodeTypeId ?? nodeType,
88
96
  },
89
97
  }),
90
98
  );
@@ -124,32 +124,44 @@ export const createFlowMetrics = () => ({
124
124
 
125
125
  /** Total number of times circuit breakers opened */
126
126
  circuitBreakerOpenTotal: Metric.counter("circuit_breaker_open_total", {
127
- description: "Total number of times circuit breakers transitioned to open state",
127
+ description:
128
+ "Total number of times circuit breakers transitioned to open state",
128
129
  }),
129
130
 
130
131
  /** Total number of times circuit breakers closed */
131
132
  circuitBreakerCloseTotal: Metric.counter("circuit_breaker_close_total", {
132
- description: "Total number of times circuit breakers transitioned to closed state",
133
+ description:
134
+ "Total number of times circuit breakers transitioned to closed state",
133
135
  }),
134
136
 
135
137
  /** Total number of requests rejected by open circuit breakers */
136
- circuitBreakerRejectedTotal: Metric.counter("circuit_breaker_rejected_total", {
137
- description: "Total number of requests rejected because circuit breaker is open",
138
- }),
138
+ circuitBreakerRejectedTotal: Metric.counter(
139
+ "circuit_breaker_rejected_total",
140
+ {
141
+ description:
142
+ "Total number of requests rejected because circuit breaker is open",
143
+ },
144
+ ),
139
145
 
140
146
  /** Total number of times circuit breakers transitioned to half-open */
141
- circuitBreakerHalfOpenTotal: Metric.counter("circuit_breaker_half_open_total", {
142
- description: "Total number of times circuit breakers transitioned to half-open state",
143
- }),
147
+ circuitBreakerHalfOpenTotal: Metric.counter(
148
+ "circuit_breaker_half_open_total",
149
+ {
150
+ description:
151
+ "Total number of times circuit breakers transitioned to half-open state",
152
+ },
153
+ ),
144
154
 
145
155
  /** Current state of circuit breakers (0=closed, 1=open, 2=half-open) */
146
156
  circuitBreakerStateGauge: Metric.gauge("circuit_breaker_state", {
147
- description: "Current circuit breaker state (0=closed, 1=open, 2=half-open)",
157
+ description:
158
+ "Current circuit breaker state (0=closed, 1=open, 2=half-open)",
148
159
  }),
149
160
 
150
161
  /** Number of failures in circuit breaker sliding window */
151
162
  circuitBreakerFailuresGauge: Metric.gauge("circuit_breaker_failures", {
152
- description: "Number of failures currently in the circuit breaker sliding window",
163
+ description:
164
+ "Number of failures currently in the circuit breaker sliding window",
153
165
  }),
154
166
  });
155
167
 
@@ -71,6 +71,76 @@ export const withExecutionContext = (context: {
71
71
  "execution.parallel_count": context.parallelCount?.toString() ?? "0",
72
72
  });
73
73
 
74
+ // ============================================================================
75
+ // Plugin Operation Tracing
76
+ // ============================================================================
77
+
78
+ /**
79
+ * Operation domains for plugin-level tracing
80
+ */
81
+ export type OperationDomain =
82
+ | "image"
83
+ | "video"
84
+ | "document"
85
+ | "ai"
86
+ | "virus-scan"
87
+ | "zip";
88
+
89
+ /**
90
+ * Wrap an Effect with a plugin operation span
91
+ *
92
+ * @param domain - The operation domain (e.g., "image", "video", "document")
93
+ * @param operation - The specific operation (e.g., "optimize", "transcode", "extract-text")
94
+ * @param attributes - Optional span attributes with operation-specific details
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * // Image optimization span
99
+ * withOperationSpan("image", "optimize", {
100
+ * "image.format": "webp",
101
+ * "image.quality": 80,
102
+ * })(imageService.optimize(inputBytes, params))
103
+ *
104
+ * // Video transcoding span
105
+ * withOperationSpan("video", "transcode", {
106
+ * "video.format": "mp4",
107
+ * "video.codec": "h264",
108
+ * })(videoService.transcode(inputBytes, params))
109
+ * ```
110
+ */
111
+ export const withOperationSpan =
112
+ <A, E, R>(
113
+ domain: OperationDomain,
114
+ operation: string,
115
+ attributes?: Record<string, unknown>,
116
+ ) =>
117
+ (effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
118
+ effect.pipe(
119
+ Effect.withSpan(`${domain}-${operation}`, {
120
+ attributes: {
121
+ "operation.domain": domain,
122
+ "operation.name": operation,
123
+ ...attributes,
124
+ },
125
+ }),
126
+ );
127
+
128
+ /**
129
+ * Add operation context to the current span
130
+ */
131
+ export const withOperationContext = (context: {
132
+ domain: OperationDomain;
133
+ operation: string;
134
+ inputSize?: number;
135
+ outputSize?: number;
136
+ }) =>
137
+ Effect.annotateCurrentSpan({
138
+ "operation.domain": context.domain,
139
+ "operation.name": context.operation,
140
+ "operation.input_size": context.inputSize?.toString() ?? "unknown",
141
+ "operation.output_size": context.outputSize?.toString() ?? "unknown",
142
+ });
143
+
74
144
  // ============================================================================
75
145
  // Circuit Breaker Tracing
76
146
  // ============================================================================
@@ -117,7 +187,8 @@ export const withCircuitBreakerContext = (context: {
117
187
  "circuit_breaker.failure_count": context.failureCount?.toString() ?? "0",
118
188
  "circuit_breaker.failure_threshold":
119
189
  context.failureThreshold?.toString() ?? "5",
120
- "circuit_breaker.reset_timeout": context.resetTimeout?.toString() ?? "30000",
190
+ "circuit_breaker.reset_timeout":
191
+ context.resetTimeout?.toString() ?? "30000",
121
192
  "circuit_breaker.decision": context.decision ?? "unknown",
122
193
  });
123
194
 
@@ -137,5 +208,6 @@ export const annotateCircuitBreakerStateChange = (event: {
137
208
  "circuit_breaker.previous_state": event.previousState,
138
209
  "circuit_breaker.new_state": event.newState,
139
210
  "circuit_breaker.failure_count": event.failureCount?.toString() ?? "0",
140
- "circuit_breaker.timestamp": event.timestamp?.toString() ?? Date.now().toString(),
211
+ "circuit_breaker.timestamp":
212
+ event.timestamp?.toString() ?? Date.now().toString(),
141
213
  });
package/src/index.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  // Main observability package exports
2
2
  export * from "./core/index.js";
3
- // Tracing specific exports
4
- export { NodeSdkLive, WebSdkLive, WorkersSdkLive } from "./core/tracing.js";
5
3
  export * from "./flow/index.js";
6
4
  export * from "./service/metrics.js";
7
5
  export * from "./storage/index.js";