autotel 2.1.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.
- package/LICENSE +21 -0
- package/README.md +1946 -0
- package/dist/chunk-2LNRY4QK.js +273 -0
- package/dist/chunk-2LNRY4QK.js.map +1 -0
- package/dist/chunk-3HENGDW2.js +587 -0
- package/dist/chunk-3HENGDW2.js.map +1 -0
- package/dist/chunk-4OAT42CA.cjs +73 -0
- package/dist/chunk-4OAT42CA.cjs.map +1 -0
- package/dist/chunk-5GWX5LFW.js +70 -0
- package/dist/chunk-5GWX5LFW.js.map +1 -0
- package/dist/chunk-5R2M36QB.js +195 -0
- package/dist/chunk-5R2M36QB.js.map +1 -0
- package/dist/chunk-5ZN622AO.js +73 -0
- package/dist/chunk-5ZN622AO.js.map +1 -0
- package/dist/chunk-77MSMAUQ.cjs +498 -0
- package/dist/chunk-77MSMAUQ.cjs.map +1 -0
- package/dist/chunk-ABPEQ6RK.cjs +596 -0
- package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
- package/dist/chunk-BWYGJKRB.js +95 -0
- package/dist/chunk-BWYGJKRB.js.map +1 -0
- package/dist/chunk-BZHG5IZ4.js +73 -0
- package/dist/chunk-BZHG5IZ4.js.map +1 -0
- package/dist/chunk-G7VZBCD6.cjs +35 -0
- package/dist/chunk-G7VZBCD6.cjs.map +1 -0
- package/dist/chunk-GVLK7YUU.cjs +30 -0
- package/dist/chunk-GVLK7YUU.cjs.map +1 -0
- package/dist/chunk-HCCXC7XG.js +205 -0
- package/dist/chunk-HCCXC7XG.js.map +1 -0
- package/dist/chunk-HE6T6FIX.cjs +203 -0
- package/dist/chunk-HE6T6FIX.cjs.map +1 -0
- package/dist/chunk-KIXWPOCO.cjs +100 -0
- package/dist/chunk-KIXWPOCO.cjs.map +1 -0
- package/dist/chunk-KVGNW3FC.js +87 -0
- package/dist/chunk-KVGNW3FC.js.map +1 -0
- package/dist/chunk-LITNXTTT.js +3 -0
- package/dist/chunk-LITNXTTT.js.map +1 -0
- package/dist/chunk-M4ANN7RL.js +114 -0
- package/dist/chunk-M4ANN7RL.js.map +1 -0
- package/dist/chunk-NC52UBR2.cjs +32 -0
- package/dist/chunk-NC52UBR2.cjs.map +1 -0
- package/dist/chunk-NHCNRQD3.cjs +212 -0
- package/dist/chunk-NHCNRQD3.cjs.map +1 -0
- package/dist/chunk-NZ72VDNY.cjs +4 -0
- package/dist/chunk-NZ72VDNY.cjs.map +1 -0
- package/dist/chunk-P6JUDYNO.js +57 -0
- package/dist/chunk-P6JUDYNO.js.map +1 -0
- package/dist/chunk-RJYY7BWX.js +1349 -0
- package/dist/chunk-RJYY7BWX.js.map +1 -0
- package/dist/chunk-TRI4V5BF.cjs +126 -0
- package/dist/chunk-TRI4V5BF.cjs.map +1 -0
- package/dist/chunk-UL33I6IS.js +139 -0
- package/dist/chunk-UL33I6IS.js.map +1 -0
- package/dist/chunk-URRW6M2C.cjs +61 -0
- package/dist/chunk-URRW6M2C.cjs.map +1 -0
- package/dist/chunk-UY3UYPBZ.cjs +77 -0
- package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
- package/dist/chunk-W3253FGB.cjs +277 -0
- package/dist/chunk-W3253FGB.cjs.map +1 -0
- package/dist/chunk-W7LHZVQF.js +26 -0
- package/dist/chunk-W7LHZVQF.js.map +1 -0
- package/dist/chunk-WBWNM6LB.cjs +1360 -0
- package/dist/chunk-WBWNM6LB.cjs.map +1 -0
- package/dist/chunk-WFJ7L2RV.js +494 -0
- package/dist/chunk-WFJ7L2RV.js.map +1 -0
- package/dist/chunk-X4RMFFMR.js +28 -0
- package/dist/chunk-X4RMFFMR.js.map +1 -0
- package/dist/chunk-Y4Y2S7BM.cjs +92 -0
- package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
- package/dist/chunk-YLPNXZFI.cjs +143 -0
- package/dist/chunk-YLPNXZFI.cjs.map +1 -0
- package/dist/chunk-YTXEZ4SD.cjs +77 -0
- package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
- package/dist/chunk-Z6ZWNWWR.js +30 -0
- package/dist/chunk-Z6ZWNWWR.js.map +1 -0
- package/dist/config.cjs +26 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +75 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db.cjs +233 -0
- package/dist/db.cjs.map +1 -0
- package/dist/db.d.cts +123 -0
- package/dist/db.d.ts +123 -0
- package/dist/db.js +228 -0
- package/dist/db.js.map +1 -0
- package/dist/decorators.cjs +67 -0
- package/dist/decorators.cjs.map +1 -0
- package/dist/decorators.d.cts +91 -0
- package/dist/decorators.d.ts +91 -0
- package/dist/decorators.js +65 -0
- package/dist/decorators.js.map +1 -0
- package/dist/event-subscriber.cjs +6 -0
- package/dist/event-subscriber.cjs.map +1 -0
- package/dist/event-subscriber.d.cts +116 -0
- package/dist/event-subscriber.d.ts +116 -0
- package/dist/event-subscriber.js +3 -0
- package/dist/event-subscriber.js.map +1 -0
- package/dist/event-testing.cjs +21 -0
- package/dist/event-testing.cjs.map +1 -0
- package/dist/event-testing.d.cts +110 -0
- package/dist/event-testing.d.ts +110 -0
- package/dist/event-testing.js +4 -0
- package/dist/event-testing.js.map +1 -0
- package/dist/event.cjs +30 -0
- package/dist/event.cjs.map +1 -0
- package/dist/event.d.cts +282 -0
- package/dist/event.d.ts +282 -0
- package/dist/event.js +13 -0
- package/dist/event.js.map +1 -0
- package/dist/exporters.cjs +17 -0
- package/dist/exporters.cjs.map +1 -0
- package/dist/exporters.d.cts +1 -0
- package/dist/exporters.d.ts +1 -0
- package/dist/exporters.js +4 -0
- package/dist/exporters.js.map +1 -0
- package/dist/functional.cjs +46 -0
- package/dist/functional.cjs.map +1 -0
- package/dist/functional.d.cts +478 -0
- package/dist/functional.d.ts +478 -0
- package/dist/functional.js +13 -0
- package/dist/functional.js.map +1 -0
- package/dist/http.cjs +189 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +169 -0
- package/dist/http.d.ts +169 -0
- package/dist/http.js +184 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +758 -0
- package/dist/index.d.ts +758 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.cjs +182 -0
- package/dist/instrumentation.cjs.map +1 -0
- package/dist/instrumentation.d.cts +49 -0
- package/dist/instrumentation.d.ts +49 -0
- package/dist/instrumentation.js +179 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/logger.cjs +19 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +146 -0
- package/dist/logger.d.ts +146 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/metric-helpers.cjs +31 -0
- package/dist/metric-helpers.cjs.map +1 -0
- package/dist/metric-helpers.d.cts +13 -0
- package/dist/metric-helpers.d.ts +13 -0
- package/dist/metric-helpers.js +6 -0
- package/dist/metric-helpers.js.map +1 -0
- package/dist/metric-testing.cjs +21 -0
- package/dist/metric-testing.cjs.map +1 -0
- package/dist/metric-testing.d.cts +110 -0
- package/dist/metric-testing.d.ts +110 -0
- package/dist/metric-testing.js +4 -0
- package/dist/metric-testing.js.map +1 -0
- package/dist/metric.cjs +26 -0
- package/dist/metric.cjs.map +1 -0
- package/dist/metric.d.cts +240 -0
- package/dist/metric.d.ts +240 -0
- package/dist/metric.js +9 -0
- package/dist/metric.js.map +1 -0
- package/dist/processors.cjs +17 -0
- package/dist/processors.cjs.map +1 -0
- package/dist/processors.d.cts +1 -0
- package/dist/processors.d.ts +1 -0
- package/dist/processors.js +4 -0
- package/dist/processors.js.map +1 -0
- package/dist/sampling.cjs +40 -0
- package/dist/sampling.cjs.map +1 -0
- package/dist/sampling.d.cts +260 -0
- package/dist/sampling.d.ts +260 -0
- package/dist/sampling.js +7 -0
- package/dist/sampling.js.map +1 -0
- package/dist/semantic-helpers.cjs +35 -0
- package/dist/semantic-helpers.cjs.map +1 -0
- package/dist/semantic-helpers.d.cts +442 -0
- package/dist/semantic-helpers.d.ts +442 -0
- package/dist/semantic-helpers.js +14 -0
- package/dist/semantic-helpers.js.map +1 -0
- package/dist/tail-sampling-processor.cjs +13 -0
- package/dist/tail-sampling-processor.cjs.map +1 -0
- package/dist/tail-sampling-processor.d.cts +27 -0
- package/dist/tail-sampling-processor.d.ts +27 -0
- package/dist/tail-sampling-processor.js +4 -0
- package/dist/tail-sampling-processor.js.map +1 -0
- package/dist/testing.cjs +286 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +291 -0
- package/dist/testing.d.ts +291 -0
- package/dist/testing.js +263 -0
- package/dist/testing.js.map +1 -0
- package/dist/trace-context-DRZdUvVY.d.cts +181 -0
- package/dist/trace-context-DRZdUvVY.d.ts +181 -0
- package/dist/trace-helpers.cjs +54 -0
- package/dist/trace-helpers.cjs.map +1 -0
- package/dist/trace-helpers.d.cts +524 -0
- package/dist/trace-helpers.d.ts +524 -0
- package/dist/trace-helpers.js +5 -0
- package/dist/trace-helpers.js.map +1 -0
- package/dist/tracer-provider.cjs +21 -0
- package/dist/tracer-provider.cjs.map +1 -0
- package/dist/tracer-provider.d.cts +169 -0
- package/dist/tracer-provider.d.ts +169 -0
- package/dist/tracer-provider.js +4 -0
- package/dist/tracer-provider.js.map +1 -0
- package/package.json +280 -0
- package/src/baggage-span-processor.test.ts +202 -0
- package/src/baggage-span-processor.ts +98 -0
- package/src/circuit-breaker.test.ts +341 -0
- package/src/circuit-breaker.ts +184 -0
- package/src/config.test.ts +94 -0
- package/src/config.ts +169 -0
- package/src/db.test.ts +252 -0
- package/src/db.ts +447 -0
- package/src/decorators.test.ts +203 -0
- package/src/decorators.ts +188 -0
- package/src/env-config.test.ts +246 -0
- package/src/env-config.ts +158 -0
- package/src/event-queue.test.ts +222 -0
- package/src/event-queue.ts +203 -0
- package/src/event-subscriber.ts +136 -0
- package/src/event-testing.ts +197 -0
- package/src/event.test.ts +718 -0
- package/src/event.ts +556 -0
- package/src/exporters.ts +96 -0
- package/src/functional.test.ts +1059 -0
- package/src/functional.ts +2295 -0
- package/src/http.test.ts +487 -0
- package/src/http.ts +424 -0
- package/src/index.ts +158 -0
- package/src/init.customization.test.ts +210 -0
- package/src/init.integrations.test.ts +366 -0
- package/src/init.openllmetry.test.ts +282 -0
- package/src/init.protocol.test.ts +215 -0
- package/src/init.ts +1426 -0
- package/src/instrumentation.test.ts +108 -0
- package/src/instrumentation.ts +308 -0
- package/src/logger.test.ts +117 -0
- package/src/logger.ts +246 -0
- package/src/metric-helpers.ts +47 -0
- package/src/metric-testing.ts +197 -0
- package/src/metric.ts +434 -0
- package/src/metrics.test.ts +205 -0
- package/src/operation-context.ts +93 -0
- package/src/processors.ts +106 -0
- package/src/rate-limiter.test.ts +199 -0
- package/src/rate-limiter.ts +98 -0
- package/src/sampling.test.ts +513 -0
- package/src/sampling.ts +428 -0
- package/src/semantic-helpers.test.ts +311 -0
- package/src/semantic-helpers.ts +584 -0
- package/src/shutdown.test.ts +311 -0
- package/src/shutdown.ts +222 -0
- package/src/stub.integration.test.ts +361 -0
- package/src/tail-sampling-processor.test.ts +226 -0
- package/src/tail-sampling-processor.ts +51 -0
- package/src/testing.ts +670 -0
- package/src/trace-context.ts +470 -0
- package/src/trace-helpers.new.test.ts +278 -0
- package/src/trace-helpers.test.ts +242 -0
- package/src/trace-helpers.ts +690 -0
- package/src/tracer-provider.test.ts +183 -0
- package/src/tracer-provider.ts +266 -0
- package/src/track.test.ts +153 -0
- package/src/track.ts +120 -0
- package/src/validation.test.ts +306 -0
- package/src/validation.ts +239 -0
- package/src/variable-name-inference.test.ts +178 -0
- package/src/variable-name-inference.ts +242 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript 5+ Decorators for autotel
|
|
3
|
+
*
|
|
4
|
+
* Provides @Trace decorator for class-based code.
|
|
5
|
+
*
|
|
6
|
+
* **Requires TypeScript 5.0+**
|
|
7
|
+
*
|
|
8
|
+
* @example Method decorator
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { Trace } from 'autotel/decorators'
|
|
11
|
+
*
|
|
12
|
+
* class OrderService {
|
|
13
|
+
* @Trace('order.create', { withMetrics: true })
|
|
14
|
+
* async createOrder(data: OrderData) {
|
|
15
|
+
* return await db.orders.create(data)
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* @Trace() // Uses method name as span name
|
|
19
|
+
* async processPayment(orderId: string) {
|
|
20
|
+
* return await stripe.charge(orderId)
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { TracingOptions, TraceContext } from './functional';
|
|
27
|
+
import { getConfig } from './config';
|
|
28
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
29
|
+
import { createTraceContext } from './trace-context';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for @Trace method decorator
|
|
33
|
+
*/
|
|
34
|
+
export interface TraceDecoratorOptions extends Omit<TracingOptions, 'name'> {
|
|
35
|
+
/**
|
|
36
|
+
* Custom span name. If not provided, uses the method name.
|
|
37
|
+
*/
|
|
38
|
+
name?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @Trace - Method decorator for fine-grained tracing
|
|
43
|
+
*
|
|
44
|
+
* Wraps a class method with automatic tracing. Supports both patterns:
|
|
45
|
+
* - Simple: method doesn't use ctx
|
|
46
|
+
* - Advanced: method accesses ctx via this.ctx
|
|
47
|
+
*
|
|
48
|
+
* @example Simple usage (no ctx)
|
|
49
|
+
* ```typescript
|
|
50
|
+
* class OrderService {
|
|
51
|
+
* @Trace()
|
|
52
|
+
* async createOrder(data: OrderData) {
|
|
53
|
+
* return await db.orders.create(data)
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example With custom name and options
|
|
59
|
+
* ```typescript
|
|
60
|
+
* class PaymentService {
|
|
61
|
+
* @Trace('payment.charge', { withMetrics: true })
|
|
62
|
+
* async chargeCard(amount: number) {
|
|
63
|
+
* return await stripe.charges.create({ amount })
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Accessing ctx
|
|
69
|
+
* ```typescript
|
|
70
|
+
* interface WithTraceContext {
|
|
71
|
+
* ctx?: TraceContext
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* class UserService {
|
|
75
|
+
* @Trace()
|
|
76
|
+
* async createUser(data: UserData) {
|
|
77
|
+
* // Access ctx via this.ctx (available during execution)
|
|
78
|
+
* const ctx = (this as unknown as WithTraceContext).ctx
|
|
79
|
+
* if (ctx) {
|
|
80
|
+
* ctx.setAttribute('user.id', data.id)
|
|
81
|
+
* }
|
|
82
|
+
* return await db.users.create(data)
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function Trace(
|
|
88
|
+
options?: TraceDecoratorOptions,
|
|
89
|
+
): <T extends (...args: unknown[]) => Promise<unknown>>(
|
|
90
|
+
originalMethod: T,
|
|
91
|
+
context: ClassMethodDecoratorContext,
|
|
92
|
+
) => T;
|
|
93
|
+
export function Trace(
|
|
94
|
+
name?: string,
|
|
95
|
+
options?: TraceDecoratorOptions,
|
|
96
|
+
): <T extends (...args: unknown[]) => Promise<unknown>>(
|
|
97
|
+
originalMethod: T,
|
|
98
|
+
context: ClassMethodDecoratorContext,
|
|
99
|
+
) => T;
|
|
100
|
+
export function Trace(
|
|
101
|
+
nameOrOptions?: string | TraceDecoratorOptions,
|
|
102
|
+
maybeOptions?: TraceDecoratorOptions,
|
|
103
|
+
): <T extends (...args: unknown[]) => Promise<unknown>>(
|
|
104
|
+
originalMethod: T,
|
|
105
|
+
context: ClassMethodDecoratorContext,
|
|
106
|
+
) => T {
|
|
107
|
+
// Parse arguments
|
|
108
|
+
const name =
|
|
109
|
+
typeof nameOrOptions === 'string' ? nameOrOptions : nameOrOptions?.name;
|
|
110
|
+
// Options are used in the returned decorator function, not here
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
112
|
+
const _options: TraceDecoratorOptions =
|
|
113
|
+
typeof nameOrOptions === 'string'
|
|
114
|
+
? maybeOptions || {}
|
|
115
|
+
: nameOrOptions || {};
|
|
116
|
+
|
|
117
|
+
// TypeScript 5+ decorator signature
|
|
118
|
+
return function <T extends (...args: unknown[]) => Promise<unknown>>(
|
|
119
|
+
originalMethod: T,
|
|
120
|
+
context: ClassMethodDecoratorContext,
|
|
121
|
+
): T {
|
|
122
|
+
const methodName = String(context.name);
|
|
123
|
+
|
|
124
|
+
// Skip if not an async function
|
|
125
|
+
// Check multiple ways to detect async functions (for different transpilation environments)
|
|
126
|
+
// TypeScript decorators run at class definition time, so we need robust detection
|
|
127
|
+
const methodStr = originalMethod?.toString() || '';
|
|
128
|
+
const isAsync =
|
|
129
|
+
originalMethod &&
|
|
130
|
+
(originalMethod.constructor?.name === 'AsyncFunction' ||
|
|
131
|
+
methodStr.trim().startsWith('async ') ||
|
|
132
|
+
(methodStr.includes('[native code]') && methodStr.includes('async')) ||
|
|
133
|
+
// Fallback: if function has async in its string representation
|
|
134
|
+
/async\s+/.test(methodStr));
|
|
135
|
+
|
|
136
|
+
if (!isAsync) {
|
|
137
|
+
// Not an async function, return as-is
|
|
138
|
+
return originalMethod;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const spanName = name || methodName;
|
|
142
|
+
|
|
143
|
+
return async function <This>(
|
|
144
|
+
this: This,
|
|
145
|
+
...args: unknown[]
|
|
146
|
+
): Promise<unknown> {
|
|
147
|
+
const config = getConfig();
|
|
148
|
+
const tracer = config.tracer;
|
|
149
|
+
|
|
150
|
+
return tracer.startActiveSpan(spanName, async (span) => {
|
|
151
|
+
try {
|
|
152
|
+
// Make ctx available via this.ctx for methods that need it
|
|
153
|
+
const ctx: TraceContext = createTraceContext(span);
|
|
154
|
+
|
|
155
|
+
const originalCtx = (this as { ctx?: TraceContext }).ctx;
|
|
156
|
+
try {
|
|
157
|
+
(this as { ctx?: TraceContext }).ctx = ctx;
|
|
158
|
+
const result = await originalMethod.apply(this, args as []);
|
|
159
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
160
|
+
return result;
|
|
161
|
+
} finally {
|
|
162
|
+
// Restore original ctx
|
|
163
|
+
if (originalCtx === undefined) {
|
|
164
|
+
delete (this as { ctx?: TraceContext }).ctx;
|
|
165
|
+
} else {
|
|
166
|
+
(this as { ctx?: TraceContext }).ctx = originalCtx;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
span.setStatus({
|
|
171
|
+
code: SpanStatusCode.ERROR,
|
|
172
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
173
|
+
});
|
|
174
|
+
span.recordException(
|
|
175
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
176
|
+
);
|
|
177
|
+
throw error;
|
|
178
|
+
} finally {
|
|
179
|
+
span.end();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} as T;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Re-export types for convenience
|
|
187
|
+
|
|
188
|
+
export { type TraceContext, type TracingOptions } from './functional';
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveOtelEnv,
|
|
4
|
+
parseResourceAttributes,
|
|
5
|
+
parseOtlpHeaders,
|
|
6
|
+
envToConfig,
|
|
7
|
+
resolveConfigFromEnv,
|
|
8
|
+
} from './env-config';
|
|
9
|
+
|
|
10
|
+
describe('env-config', () => {
|
|
11
|
+
const originalEnv = process.env;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Reset environment before each test
|
|
15
|
+
process.env = { ...originalEnv };
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Restore original environment
|
|
20
|
+
process.env = originalEnv;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('resolveOtelEnv', () => {
|
|
24
|
+
it('should resolve standard OTEL env vars', () => {
|
|
25
|
+
process.env.OTEL_SERVICE_NAME = 'test-service';
|
|
26
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';
|
|
27
|
+
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = 'http';
|
|
28
|
+
|
|
29
|
+
const env = resolveOtelEnv();
|
|
30
|
+
|
|
31
|
+
expect(env.OTEL_SERVICE_NAME).toBe('test-service');
|
|
32
|
+
expect(env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe('http://localhost:4318');
|
|
33
|
+
expect(env.OTEL_EXPORTER_OTLP_PROTOCOL).toBe('http');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return undefined for unset env vars', () => {
|
|
37
|
+
const env = resolveOtelEnv();
|
|
38
|
+
|
|
39
|
+
expect(env.OTEL_SERVICE_NAME).toBeUndefined();
|
|
40
|
+
expect(env.OTEL_EXPORTER_OTLP_ENDPOINT).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should validate protocol enum', () => {
|
|
44
|
+
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = 'grpc';
|
|
45
|
+
const env = resolveOtelEnv();
|
|
46
|
+
expect(env.OTEL_EXPORTER_OTLP_PROTOCOL).toBe('grpc');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('parseResourceAttributes', () => {
|
|
51
|
+
it('should parse comma-separated key=value pairs', () => {
|
|
52
|
+
const input = 'service.version=1.0.0,deployment.environment=production';
|
|
53
|
+
const result = parseResourceAttributes(input);
|
|
54
|
+
|
|
55
|
+
expect(result).toEqual({
|
|
56
|
+
'service.version': '1.0.0',
|
|
57
|
+
'deployment.environment': 'production',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should handle single attribute', () => {
|
|
62
|
+
const result = parseResourceAttributes('team=backend');
|
|
63
|
+
expect(result).toEqual({ team: 'backend' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle empty string', () => {
|
|
67
|
+
expect(parseResourceAttributes('')).toEqual({});
|
|
68
|
+
expect(parseResourceAttributes(' ')).toEqual({});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle undefined', () => {
|
|
72
|
+
expect(parseResourceAttributes()).toEqual({});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should skip invalid pairs without =', () => {
|
|
76
|
+
const result = parseResourceAttributes('valid=value,invalid,another=ok');
|
|
77
|
+
expect(result).toEqual({
|
|
78
|
+
valid: 'value',
|
|
79
|
+
another: 'ok',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle whitespace around keys and values', () => {
|
|
84
|
+
const result = parseResourceAttributes(' key1 = value1 , key2 = value2 ');
|
|
85
|
+
expect(result).toEqual({
|
|
86
|
+
key1: 'value1',
|
|
87
|
+
key2: 'value2',
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should skip empty pairs', () => {
|
|
92
|
+
const result = parseResourceAttributes('key1=value1,,key2=value2');
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
key1: 'value1',
|
|
95
|
+
key2: 'value2',
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle values with = in them (takes first = as delimiter)', () => {
|
|
100
|
+
const result = parseResourceAttributes('key=value=with=equals');
|
|
101
|
+
expect(result).toEqual({
|
|
102
|
+
key: 'value=with=equals',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('parseOtlpHeaders', () => {
|
|
108
|
+
it('should parse comma-separated header pairs', () => {
|
|
109
|
+
const input = 'api-key=secret123,x-custom-header=value';
|
|
110
|
+
const result = parseOtlpHeaders(input);
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
'api-key': 'secret123',
|
|
114
|
+
'x-custom-header': 'value',
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle single header', () => {
|
|
119
|
+
const result = parseOtlpHeaders('authorization=Bearer token');
|
|
120
|
+
expect(result).toEqual({ authorization: 'Bearer token' });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should handle empty string', () => {
|
|
124
|
+
expect(parseOtlpHeaders('')).toEqual({});
|
|
125
|
+
expect(parseOtlpHeaders(' ')).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle undefined', () => {
|
|
129
|
+
expect(parseOtlpHeaders()).toEqual({});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should skip invalid pairs', () => {
|
|
133
|
+
const result = parseOtlpHeaders('valid=value,invalid,another=ok');
|
|
134
|
+
expect(result).toEqual({
|
|
135
|
+
valid: 'value',
|
|
136
|
+
another: 'ok',
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle whitespace', () => {
|
|
141
|
+
const result = parseOtlpHeaders(' key1 = value1 , key2 = value2 ');
|
|
142
|
+
expect(result).toEqual({
|
|
143
|
+
key1: 'value1',
|
|
144
|
+
key2: 'value2',
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('envToConfig', () => {
|
|
150
|
+
it('should map OTEL_SERVICE_NAME to service', () => {
|
|
151
|
+
const config = envToConfig({
|
|
152
|
+
OTEL_SERVICE_NAME: 'test-service',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(config.service).toBe('test-service');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should map OTEL_EXPORTER_OTLP_ENDPOINT to endpoint', () => {
|
|
159
|
+
const config = envToConfig({
|
|
160
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: 'http://localhost:4318',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(config.endpoint).toBe('http://localhost:4318');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should map OTEL_EXPORTER_OTLP_PROTOCOL to protocol', () => {
|
|
167
|
+
const config = envToConfig({
|
|
168
|
+
OTEL_EXPORTER_OTLP_PROTOCOL: 'grpc',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(config.protocol).toBe('grpc');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should parse OTEL_EXPORTER_OTLP_HEADERS', () => {
|
|
175
|
+
const config = envToConfig({
|
|
176
|
+
OTEL_EXPORTER_OTLP_HEADERS: 'api-key=secret,x-custom=value',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(config.otlpHeaders).toEqual({
|
|
180
|
+
'api-key': 'secret',
|
|
181
|
+
'x-custom': 'value',
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should parse OTEL_RESOURCE_ATTRIBUTES', () => {
|
|
186
|
+
const config = envToConfig({
|
|
187
|
+
OTEL_RESOURCE_ATTRIBUTES:
|
|
188
|
+
'service.version=1.0.0,deployment.environment=prod',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(config.resourceAttributes).toEqual({
|
|
192
|
+
'service.version': '1.0.0',
|
|
193
|
+
'deployment.environment': 'prod',
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should return empty config for no env vars', () => {
|
|
198
|
+
const config = envToConfig({});
|
|
199
|
+
expect(config).toEqual({});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('resolveConfigFromEnv', () => {
|
|
204
|
+
it('should return config from env vars', () => {
|
|
205
|
+
process.env.OTEL_SERVICE_NAME = 'test-service';
|
|
206
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';
|
|
207
|
+
|
|
208
|
+
const config = resolveConfigFromEnv();
|
|
209
|
+
|
|
210
|
+
expect(config.service).toBe('test-service');
|
|
211
|
+
expect(config.endpoint).toBe('http://localhost:4318');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should work with no env vars set', () => {
|
|
215
|
+
const config = resolveConfigFromEnv();
|
|
216
|
+
expect(config).toEqual({});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should parse complex real-world scenario', () => {
|
|
220
|
+
process.env.OTEL_SERVICE_NAME = 'my-api';
|
|
221
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'https://api.honeycomb.io';
|
|
222
|
+
process.env.OTEL_EXPORTER_OTLP_HEADERS =
|
|
223
|
+
'x-honeycomb-team=abc123,x-honeycomb-dataset=production';
|
|
224
|
+
process.env.OTEL_RESOURCE_ATTRIBUTES =
|
|
225
|
+
'service.version=1.2.3,deployment.environment=production,team=backend';
|
|
226
|
+
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = 'http';
|
|
227
|
+
|
|
228
|
+
const config = resolveConfigFromEnv();
|
|
229
|
+
|
|
230
|
+
expect(config).toEqual({
|
|
231
|
+
service: 'my-api',
|
|
232
|
+
endpoint: 'https://api.honeycomb.io',
|
|
233
|
+
protocol: 'http',
|
|
234
|
+
otlpHeaders: {
|
|
235
|
+
'x-honeycomb-team': 'abc123',
|
|
236
|
+
'x-honeycomb-dataset': 'production',
|
|
237
|
+
},
|
|
238
|
+
resourceAttributes: {
|
|
239
|
+
'service.version': '1.2.3',
|
|
240
|
+
'deployment.environment': 'production',
|
|
241
|
+
team: 'backend',
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { resolve } from 'node-env-resolver';
|
|
2
|
+
import { string, url, optional } from 'node-env-resolver/validators';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Standard OpenTelemetry environment variables
|
|
6
|
+
*/
|
|
7
|
+
export interface OtelEnvVars {
|
|
8
|
+
OTEL_SERVICE_NAME?: string;
|
|
9
|
+
OTEL_EXPORTER_OTLP_ENDPOINT?: string;
|
|
10
|
+
OTEL_EXPORTER_OTLP_HEADERS?: string;
|
|
11
|
+
OTEL_RESOURCE_ATTRIBUTES?: string;
|
|
12
|
+
OTEL_EXPORTER_OTLP_PROTOCOL?: 'http' | 'grpc';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parsed resource attributes as key-value pairs
|
|
17
|
+
*/
|
|
18
|
+
export interface ResourceAttributes {
|
|
19
|
+
[key: string]: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parsed OTLP headers as key-value pairs
|
|
24
|
+
*/
|
|
25
|
+
export interface OtlpHeaders {
|
|
26
|
+
[key: string]: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Environment-resolved configuration (subset of AutotelConfig)
|
|
31
|
+
* Defined locally to avoid circular dependency with init.ts
|
|
32
|
+
*/
|
|
33
|
+
export interface EnvConfig {
|
|
34
|
+
service?: string;
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
protocol?: 'http' | 'grpc';
|
|
37
|
+
otlpHeaders?: Record<string, string>;
|
|
38
|
+
resourceAttributes?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve OpenTelemetry environment variables using node-env-resolver
|
|
43
|
+
*/
|
|
44
|
+
export function resolveOtelEnv(): OtelEnvVars {
|
|
45
|
+
return resolve({
|
|
46
|
+
OTEL_SERVICE_NAME: string({ optional: true }),
|
|
47
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: url({ optional: true }),
|
|
48
|
+
OTEL_EXPORTER_OTLP_HEADERS: string({ optional: true }),
|
|
49
|
+
OTEL_RESOURCE_ATTRIBUTES: string({ optional: true }),
|
|
50
|
+
OTEL_EXPORTER_OTLP_PROTOCOL: optional(['http', 'grpc'] as const),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse OTEL_RESOURCE_ATTRIBUTES from comma-separated key=value pairs
|
|
56
|
+
* Example: "service.version=1.0.0,deployment.environment=production"
|
|
57
|
+
*/
|
|
58
|
+
export function parseResourceAttributes(
|
|
59
|
+
input: string | undefined,
|
|
60
|
+
): ResourceAttributes {
|
|
61
|
+
if (!input || input.trim() === '') {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const attributes: ResourceAttributes = {};
|
|
66
|
+
const pairs = input.split(',');
|
|
67
|
+
|
|
68
|
+
for (const pair of pairs) {
|
|
69
|
+
const trimmedPair = pair.trim();
|
|
70
|
+
if (!trimmedPair) continue;
|
|
71
|
+
|
|
72
|
+
const equalIndex = trimmedPair.indexOf('=');
|
|
73
|
+
if (equalIndex === -1) {
|
|
74
|
+
// Invalid format, skip this pair
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const key = trimmedPair.slice(0, equalIndex).trim();
|
|
79
|
+
const value = trimmedPair.slice(equalIndex + 1).trim();
|
|
80
|
+
|
|
81
|
+
if (key && value) {
|
|
82
|
+
attributes[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return attributes;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Parse OTEL_EXPORTER_OTLP_HEADERS from comma-separated key=value pairs
|
|
91
|
+
* Example: "api-key=secret123,x-custom-header=value"
|
|
92
|
+
*/
|
|
93
|
+
export function parseOtlpHeaders(input: string | undefined): OtlpHeaders {
|
|
94
|
+
if (!input || input.trim() === '') {
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const headers: OtlpHeaders = {};
|
|
99
|
+
const pairs = input.split(',');
|
|
100
|
+
|
|
101
|
+
for (const pair of pairs) {
|
|
102
|
+
const trimmedPair = pair.trim();
|
|
103
|
+
if (!trimmedPair) continue;
|
|
104
|
+
|
|
105
|
+
const equalIndex = trimmedPair.indexOf('=');
|
|
106
|
+
if (equalIndex === -1) {
|
|
107
|
+
// Invalid format, skip this pair
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const key = trimmedPair.slice(0, equalIndex).trim();
|
|
112
|
+
const value = trimmedPair.slice(equalIndex + 1).trim();
|
|
113
|
+
|
|
114
|
+
if (key && value) {
|
|
115
|
+
headers[key] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return headers;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convert resolved environment variables to config
|
|
124
|
+
*/
|
|
125
|
+
export function envToConfig(env: OtelEnvVars): EnvConfig {
|
|
126
|
+
const config: EnvConfig = {};
|
|
127
|
+
|
|
128
|
+
if (env.OTEL_SERVICE_NAME) {
|
|
129
|
+
config.service = env.OTEL_SERVICE_NAME;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
133
|
+
config.endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (env.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
137
|
+
config.protocol = env.OTEL_EXPORTER_OTLP_PROTOCOL;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (env.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
141
|
+
config.otlpHeaders = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const resourceAttrs = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
|
|
145
|
+
if (Object.keys(resourceAttrs).length > 0) {
|
|
146
|
+
config.resourceAttributes = resourceAttrs;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return config;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Main function to resolve config from environment variables
|
|
154
|
+
*/
|
|
155
|
+
export function resolveConfigFromEnv(): EnvConfig {
|
|
156
|
+
const env = resolveOtelEnv();
|
|
157
|
+
return envToConfig(env);
|
|
158
|
+
}
|