@asenajs/asena-otel 1.0.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 +331 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/OtelConfig.d.ts +15 -0
- package/dist/lib/OtelConfig.js +2 -0
- package/dist/lib/OtelConfig.js.map +1 -0
- package/dist/lib/constants/OtelConstants.d.ts +11 -0
- package/dist/lib/constants/OtelConstants.js +12 -0
- package/dist/lib/constants/OtelConstants.js.map +1 -0
- package/dist/lib/decorators/Otel.d.ts +20 -0
- package/dist/lib/decorators/Otel.js +28 -0
- package/dist/lib/decorators/Otel.js.map +1 -0
- package/dist/lib/decorators/index.d.ts +1 -0
- package/dist/lib/decorators/index.js +2 -0
- package/dist/lib/decorators/index.js.map +1 -0
- package/dist/lib/middleware/OtelTracingMiddleware.d.ts +10 -0
- package/dist/lib/middleware/OtelTracingMiddleware.js +94 -0
- package/dist/lib/middleware/OtelTracingMiddleware.js.map +1 -0
- package/dist/lib/postprocessor/OtelTracingPostProcessor.d.ts +19 -0
- package/dist/lib/postprocessor/OtelTracingPostProcessor.js +164 -0
- package/dist/lib/postprocessor/OtelTracingPostProcessor.js.map +1 -0
- package/dist/lib/samplers/index.d.ts +8 -0
- package/dist/lib/samplers/index.js +13 -0
- package/dist/lib/samplers/index.js.map +1 -0
- package/dist/lib/services/OtelService.d.ts +11 -0
- package/dist/lib/services/OtelService.js +62 -0
- package/dist/lib/services/OtelService.js.map +1 -0
- package/dist/lib/shared/OtelRuntimeConfig.d.ts +8 -0
- package/dist/lib/shared/OtelRuntimeConfig.js +21 -0
- package/dist/lib/shared/OtelRuntimeConfig.js.map +1 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AsenaJs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# @asenajs/asena-otel
|
|
2
|
+
|
|
3
|
+
OpenTelemetry integration for AsenaJS — automatic HTTP tracing, method-level auto-tracing, metrics, and distributed tracing support.
|
|
4
|
+
|
|
5
|
+
A single request automatically produces a full waterfall trace:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
GET /api/users (SERVER)
|
|
9
|
+
└─ UserController.list (INTERNAL)
|
|
10
|
+
└─ UserService.getAll (INTERNAL)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @asenajs/asena-otel @opentelemetry/api @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/sdk-metrics @opentelemetry/semantic-conventions @opentelemetry/context-async-hooks
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Step 1: Create an @Otel class
|
|
22
|
+
|
|
23
|
+
Create a class that extends `OtelTracingPostProcessor` and apply the `@Otel` decorator with your configuration. Asena automatically discovers and initializes it during bootstrap.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// src/otel/AppOtel.ts
|
|
27
|
+
import { Otel, OtelTracingPostProcessor } from '@asenajs/asena-otel';
|
|
28
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
29
|
+
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
30
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
31
|
+
|
|
32
|
+
@Otel({
|
|
33
|
+
serviceName: 'my-app',
|
|
34
|
+
serviceVersion: '1.0.0',
|
|
35
|
+
traceExporter: new OTLPTraceExporter({
|
|
36
|
+
url: 'http://localhost:4318/v1/traces',
|
|
37
|
+
}),
|
|
38
|
+
metricReader: new PeriodicExportingMetricReader({
|
|
39
|
+
exporter: new OTLPMetricExporter({
|
|
40
|
+
url: 'http://localhost:4318/v1/metrics',
|
|
41
|
+
}),
|
|
42
|
+
}),
|
|
43
|
+
autoTrace: {
|
|
44
|
+
services: true, // auto-trace @Service methods
|
|
45
|
+
controllers: true, // auto-trace @Controller methods
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
export class AppOtel extends OtelTracingPostProcessor {}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Step 2: Register OtelTracingMiddleware in your Config
|
|
52
|
+
|
|
53
|
+
Add `OtelTracingMiddleware` to your config's `globalMiddlewares()`. This is required so that all HTTP requests are traced automatically.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Config } from '@asenajs/asena/decorators';
|
|
57
|
+
import { ConfigService, type Context, HttpException } from '@asenajs/ergenecore';
|
|
58
|
+
import { OtelTracingMiddleware } from '@asenajs/asena-otel';
|
|
59
|
+
import { AppCorsMiddleware } from '../middlewares/AppCorsMiddleware';
|
|
60
|
+
|
|
61
|
+
@Config()
|
|
62
|
+
export class AppConfig extends ConfigService {
|
|
63
|
+
|
|
64
|
+
public globalMiddlewares() {
|
|
65
|
+
return [
|
|
66
|
+
OtelTracingMiddleware, // traces all HTTP requests automatically
|
|
67
|
+
AppCorsMiddleware,
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public onError(error: Error, context: Context) {
|
|
72
|
+
if (error instanceof HttpException) {
|
|
73
|
+
return context.send(error.body, error.status);
|
|
74
|
+
}
|
|
75
|
+
return context.send({ error: 'Internal Server Error' }, 500);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
That's it. Asena's IoC container automatically discovers `AppOtel`, `OtelService`, and `OtelTracingMiddleware`. All HTTP requests are traced, service methods are auto-traced, and metrics are collected — without changing any business logic.
|
|
82
|
+
|
|
83
|
+
## Components
|
|
84
|
+
|
|
85
|
+
### @Otel Decorator
|
|
86
|
+
|
|
87
|
+
Configures an `OtelTracingPostProcessor` subclass with OpenTelemetry options. Apply it to a class extending `OtelTracingPostProcessor`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { Otel, OtelTracingPostProcessor } from '@asenajs/asena-otel';
|
|
91
|
+
|
|
92
|
+
@Otel({
|
|
93
|
+
serviceName: 'my-app',
|
|
94
|
+
traceExporter: exporter,
|
|
95
|
+
autoTrace: { services: true, controllers: true },
|
|
96
|
+
})
|
|
97
|
+
export class AppOtel extends OtelTracingPostProcessor {}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The decorator stores the options as metadata and applies `@PostProcessor()` automatically. During bootstrap, `@PostConstruct()` in `OtelTracingPostProcessor` reads the metadata and initializes the OpenTelemetry SDK — tracer provider, meter provider, context manager, and shutdown hooks.
|
|
101
|
+
|
|
102
|
+
### OtelService
|
|
103
|
+
|
|
104
|
+
Injectable `@Service` that provides access to OpenTelemetry tracer and meter. Use it for custom spans and distributed tracing. Asena automatically discovers and registers it.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { Inject } from '@asenajs/asena/decorators/ioc';
|
|
108
|
+
import type { OtelService } from '@asenajs/asena-otel';
|
|
109
|
+
|
|
110
|
+
@Service()
|
|
111
|
+
export class OrderService {
|
|
112
|
+
|
|
113
|
+
@Inject('OtelService')
|
|
114
|
+
private otelService: OtelService;
|
|
115
|
+
|
|
116
|
+
async processOrder(orderId: string) {
|
|
117
|
+
return this.otelService.withSpan('process-order', async (span) => {
|
|
118
|
+
span.setAttribute('order.id', orderId);
|
|
119
|
+
// ... business logic
|
|
120
|
+
return { success: true };
|
|
121
|
+
});
|
|
122
|
+
// span automatically ends, errors are recorded
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**API:**
|
|
129
|
+
- `tracer` — OpenTelemetry `Tracer` instance
|
|
130
|
+
- `meter` — OpenTelemetry `Meter` instance
|
|
131
|
+
- `withSpan(name, fn)` — creates a span, sets OK/ERROR status, records exceptions, ends span automatically
|
|
132
|
+
- `getActiveSpan()` — returns current active span (or undefined)
|
|
133
|
+
- `injectTraceContext(headers?)` — injects W3C `traceparent` header into a headers object for distributed tracing (see [Outgoing Request Context Propagation](#outgoing-request-context-propagation))
|
|
134
|
+
|
|
135
|
+
**Expression injection** (for direct tracer/meter access):
|
|
136
|
+
```typescript
|
|
137
|
+
@Inject('OtelService', (s) => s.tracer)
|
|
138
|
+
private tracer: Tracer;
|
|
139
|
+
|
|
140
|
+
@Inject('OtelService', (s) => s.meter)
|
|
141
|
+
private meter: Meter;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### OtelTracingMiddleware
|
|
145
|
+
|
|
146
|
+
`@Middleware` that automatically traces all HTTP requests. Register in your Config's `globalMiddlewares()` to enable request tracing.
|
|
147
|
+
|
|
148
|
+
**Creates for each request:**
|
|
149
|
+
- A `SERVER` span named `"{METHOD} {PATH}"` (e.g., `GET /api/users`)
|
|
150
|
+
- Attributes: `http.request.method`, `url.path`, `http.route` (after route matching), `http.response.status_code`
|
|
151
|
+
- Metrics: `http.server.request.count` (Counter), `http.server.request.duration` (Histogram)
|
|
152
|
+
- Extracts W3C `traceparent` header from incoming requests for distributed tracing
|
|
153
|
+
|
|
154
|
+
### OtelTracingPostProcessor
|
|
155
|
+
|
|
156
|
+
`@PostProcessor` that wraps Service and Controller methods with tracing spans via JavaScript Proxy.
|
|
157
|
+
|
|
158
|
+
- Creates `INTERNAL` spans named `"{ClassName}.{methodName}"` (e.g., `UserService.getAll`)
|
|
159
|
+
- Spans are automatically children of the HTTP span (proper waterfall hierarchy)
|
|
160
|
+
- Controlled via `autoTrace` config in the `@Otel` decorator options
|
|
161
|
+
- Skips private methods (`_` prefix), constructors, and Symbol properties
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
### AsenaOtelOptions
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface AsenaOtelOptions {
|
|
169
|
+
serviceName: string; // Required: identifies your service
|
|
170
|
+
serviceVersion?: string; // Optional: defaults to '0.0.0'
|
|
171
|
+
traceExporter: SpanExporter; // Required: where to send spans
|
|
172
|
+
metricReader?: MetricReader; // Optional: enables metrics collection
|
|
173
|
+
autoTrace?: AutoTraceConfig; // Optional: auto-trace settings
|
|
174
|
+
sampler?: Sampler; // Optional: custom sampling strategy
|
|
175
|
+
ignoreRoutes?: string[]; // Optional: routes to exclude from tracing
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface AutoTraceConfig {
|
|
179
|
+
services?: boolean; // auto-trace @Service methods (default: false)
|
|
180
|
+
controllers?: boolean; // auto-trace @Controller methods (default: false)
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Sampling
|
|
185
|
+
|
|
186
|
+
Use `ratioBasedSampler()` for production environments to control trace volume:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { Otel, OtelTracingPostProcessor, ratioBasedSampler } from '@asenajs/asena-otel';
|
|
190
|
+
|
|
191
|
+
@Otel({
|
|
192
|
+
serviceName: 'my-app',
|
|
193
|
+
traceExporter: exporter,
|
|
194
|
+
sampler: ratioBasedSampler(0.1), // sample 10% of traces
|
|
195
|
+
autoTrace: { services: true, controllers: true },
|
|
196
|
+
})
|
|
197
|
+
export class AppOtel extends OtelTracingPostProcessor {}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`ratioBasedSampler(ratio)` creates a `ParentBasedSampler` wrapping `TraceIdRatioBasedSampler`. Root spans are sampled at the given ratio (0.0–1.0); child spans respect the parent's decision.
|
|
201
|
+
|
|
202
|
+
## Route Exclusion
|
|
203
|
+
|
|
204
|
+
Use `ignoreRoutes` to skip tracing on specific paths (e.g., health checks, metrics endpoints):
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
@Otel({
|
|
208
|
+
serviceName: 'my-app',
|
|
209
|
+
traceExporter: exporter,
|
|
210
|
+
ignoreRoutes: ['/health', '/metrics', '/admin/*'],
|
|
211
|
+
})
|
|
212
|
+
export class AppOtel extends OtelTracingPostProcessor {}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **Exact match:** `/health` matches only `/health`
|
|
216
|
+
- **Wildcard suffix:** `/admin/*` matches `/admin/` and all sub-paths
|
|
217
|
+
|
|
218
|
+
Ignored routes produce no spans and no metrics.
|
|
219
|
+
|
|
220
|
+
## Outgoing Request Context Propagation
|
|
221
|
+
|
|
222
|
+
`@asenajs/asena-otel` automatically traces **incoming** HTTP requests via `OtelTracingMiddleware` (extracts W3C `traceparent` header). However, **outgoing** HTTP calls (e.g., `fetch` to another service) are **not** automatically instrumented.
|
|
223
|
+
|
|
224
|
+
Use `OtelService.injectTraceContext()` to manually propagate trace context to downstream services:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
@Service()
|
|
228
|
+
export class PaymentClient {
|
|
229
|
+
|
|
230
|
+
@Inject('OtelService')
|
|
231
|
+
private otelService: OtelService;
|
|
232
|
+
|
|
233
|
+
async charge(payload: ChargeRequest) {
|
|
234
|
+
// injectTraceContext() adds the W3C traceparent header
|
|
235
|
+
const headers = this.otelService.injectTraceContext({
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const res = await fetch('http://payment-service/api/charge', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers,
|
|
242
|
+
body: JSON.stringify(payload),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return res.json();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
For full visibility, combine with `withSpan()` to create a dedicated span for the outgoing call:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
async charge(payload: ChargeRequest) {
|
|
255
|
+
return this.otelService.withSpan('call-payment-service', async (span) => {
|
|
256
|
+
span.setAttribute('service.target', 'payment-service');
|
|
257
|
+
|
|
258
|
+
const headers = this.otelService.injectTraceContext();
|
|
259
|
+
const res = await fetch('http://payment-service/api/charge', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
262
|
+
body: JSON.stringify(payload),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return res.json();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This writes a `traceparent` header in the format `00-{traceId}-{spanId}-{traceFlags}`. The downstream service extracts this header to continue the same trace, enabling end-to-end distributed tracing across microservices.
|
|
271
|
+
|
|
272
|
+
## Testing
|
|
273
|
+
|
|
274
|
+
Use `InMemorySpanExporter` and `InMemoryMetricExporter` for testing:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Otel, OtelTracingPostProcessor } from '@asenajs/asena-otel';
|
|
278
|
+
import { InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
279
|
+
import { InMemoryMetricExporter, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
280
|
+
|
|
281
|
+
const spanExporter = new InMemorySpanExporter();
|
|
282
|
+
const metricExporter = new InMemoryMetricExporter();
|
|
283
|
+
const metricReader = new PeriodicExportingMetricReader({
|
|
284
|
+
exporter: metricExporter,
|
|
285
|
+
exportIntervalMillis: 100,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
@Otel({
|
|
289
|
+
serviceName: 'test-service',
|
|
290
|
+
traceExporter: spanExporter,
|
|
291
|
+
metricReader,
|
|
292
|
+
autoTrace: { services: true, controllers: true },
|
|
293
|
+
})
|
|
294
|
+
export class TestOtel extends OtelTracingPostProcessor {}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
After making requests, flush spans (`BatchSpanProcessor` is lazy):
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { trace } from '@opentelemetry/api';
|
|
301
|
+
|
|
302
|
+
const provider = trace.getTracerProvider() as any;
|
|
303
|
+
await provider.forceFlush?.();
|
|
304
|
+
const spans = spanExporter.getFinishedSpans();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
For metrics, wait for periodic export then read from exporter:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
await metricReader.forceFlush();
|
|
311
|
+
const metrics = metricExporter.getMetrics();
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Verifying parent-child hierarchy
|
|
315
|
+
|
|
316
|
+
In OpenTelemetry SDK v2, use `parentSpanContext` (not `parentSpanId`):
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
const httpSpan = spans.find(s => s.kind === SpanKind.SERVER);
|
|
320
|
+
const serviceSpan = spans.find(s => s.name.includes('UserService'));
|
|
321
|
+
|
|
322
|
+
// Same trace
|
|
323
|
+
expect(serviceSpan.spanContext().traceId).toBe(httpSpan.spanContext().traceId);
|
|
324
|
+
|
|
325
|
+
// Parent-child link (SDK v2)
|
|
326
|
+
expect(serviceSpan.parentSpanContext?.spanId).toBe(httpSpan.spanContext().spanId);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Otel } from './lib/decorators';
|
|
2
|
+
export { OtelConstants } from './lib/constants/OtelConstants';
|
|
3
|
+
export { OtelTracingPostProcessor } from './lib/postprocessor/OtelTracingPostProcessor';
|
|
4
|
+
export { OtelService } from './lib/services/OtelService';
|
|
5
|
+
export { OtelTracingMiddleware } from './lib/middleware/OtelTracingMiddleware';
|
|
6
|
+
export { ratioBasedSampler } from './lib/samplers';
|
|
7
|
+
export { isRouteIgnored } from './lib/shared/OtelRuntimeConfig';
|
|
8
|
+
export type { AsenaOtelOptions, AutoTraceConfig } from './lib/types';
|
|
9
|
+
export type { Sampler } from '@opentelemetry/sdk-trace-base';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Decorator
|
|
2
|
+
export { Otel } from './lib/decorators';
|
|
3
|
+
// Constants
|
|
4
|
+
export { OtelConstants } from './lib/constants/OtelConstants';
|
|
5
|
+
// PostProcessor
|
|
6
|
+
export { OtelTracingPostProcessor } from './lib/postprocessor/OtelTracingPostProcessor';
|
|
7
|
+
// Service
|
|
8
|
+
export { OtelService } from './lib/services/OtelService';
|
|
9
|
+
// Middleware
|
|
10
|
+
export { OtelTracingMiddleware } from './lib/middleware/OtelTracingMiddleware';
|
|
11
|
+
// Samplers
|
|
12
|
+
export { ratioBasedSampler } from './lib/samplers';
|
|
13
|
+
// Runtime config utilities
|
|
14
|
+
export { isRouteIgnored } from './lib/shared/OtelRuntimeConfig';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,YAAY;AACZ,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,YAAY;AACZ,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,gBAAgB;AAChB,OAAO,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAC;AAExF,UAAU;AACV,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,aAAa;AACb,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAE/E,WAAW;AACX,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,2BAA2B;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Sampler, SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
import type { MetricReader } from '@opentelemetry/sdk-metrics';
|
|
3
|
+
export interface AutoTraceConfig {
|
|
4
|
+
services?: boolean;
|
|
5
|
+
controllers?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface AsenaOtelOptions {
|
|
8
|
+
serviceName: string;
|
|
9
|
+
serviceVersion?: string;
|
|
10
|
+
traceExporter: SpanExporter;
|
|
11
|
+
metricReader?: MetricReader;
|
|
12
|
+
autoTrace?: AutoTraceConfig;
|
|
13
|
+
sampler?: Sampler;
|
|
14
|
+
ignoreRoutes?: string[];
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelConfig.js","sourceRoot":"","sources":["../../lib/OtelConfig.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata constants for OTel decorators.
|
|
3
|
+
* Uses Symbols to prevent naming collisions.
|
|
4
|
+
*/
|
|
5
|
+
export declare class OtelConstants {
|
|
6
|
+
/**
|
|
7
|
+
* @Otel() decorator metadata key.
|
|
8
|
+
* Stores AsenaOtelOptions on the PostProcessor subclass.
|
|
9
|
+
*/
|
|
10
|
+
static readonly OptionsKey: unique symbol;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata constants for OTel decorators.
|
|
3
|
+
* Uses Symbols to prevent naming collisions.
|
|
4
|
+
*/
|
|
5
|
+
export class OtelConstants {
|
|
6
|
+
/**
|
|
7
|
+
* @Otel() decorator metadata key.
|
|
8
|
+
* Stores AsenaOtelOptions on the PostProcessor subclass.
|
|
9
|
+
*/
|
|
10
|
+
static OptionsKey = Symbol('otel:options');
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=OtelConstants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelConstants.js","sourceRoot":"","sources":["../../../lib/constants/OtelConstants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,aAAa;IAExB;;;OAGG;IACI,MAAM,CAAU,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AsenaOtelOptions } from '../OtelConfig';
|
|
2
|
+
/**
|
|
3
|
+
* Decorator that configures an OTel Tracing PostProcessor.
|
|
4
|
+
*
|
|
5
|
+
* Apply to a class extending `OtelTracingPostProcessor` to automatically:
|
|
6
|
+
* 1. Initialize the OpenTelemetry SDK (tracer + meter providers)
|
|
7
|
+
* 2. Auto-trace Service and Controller methods via Proxy
|
|
8
|
+
* 3. Register shutdown hooks for graceful cleanup
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* @Otel({
|
|
13
|
+
* serviceName: 'my-app',
|
|
14
|
+
* traceExporter: new OTLPTraceExporter({ url: 'http://jaeger:4318/v1/traces' }),
|
|
15
|
+
* autoTrace: { services: true, controllers: true },
|
|
16
|
+
* })
|
|
17
|
+
* export class AppOtel extends OtelTracingPostProcessor {}
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function Otel(options: AsenaOtelOptions): <T extends new (...args: any[]) => any>(target: T) => T;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PostProcessor } from '@asenajs/asena/decorators';
|
|
2
|
+
import { defineTypedMetadata } from '@asenajs/asena/utils';
|
|
3
|
+
import { OtelConstants } from '../constants/OtelConstants';
|
|
4
|
+
/**
|
|
5
|
+
* Decorator that configures an OTel Tracing PostProcessor.
|
|
6
|
+
*
|
|
7
|
+
* Apply to a class extending `OtelTracingPostProcessor` to automatically:
|
|
8
|
+
* 1. Initialize the OpenTelemetry SDK (tracer + meter providers)
|
|
9
|
+
* 2. Auto-trace Service and Controller methods via Proxy
|
|
10
|
+
* 3. Register shutdown hooks for graceful cleanup
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @Otel({
|
|
15
|
+
* serviceName: 'my-app',
|
|
16
|
+
* traceExporter: new OTLPTraceExporter({ url: 'http://jaeger:4318/v1/traces' }),
|
|
17
|
+
* autoTrace: { services: true, controllers: true },
|
|
18
|
+
* })
|
|
19
|
+
* export class AppOtel extends OtelTracingPostProcessor {}
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function Otel(options) {
|
|
23
|
+
return function (target) {
|
|
24
|
+
defineTypedMetadata(OtelConstants.OptionsKey, options, target);
|
|
25
|
+
return PostProcessor()(target);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=Otel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Otel.js","sourceRoot":"","sources":["../../../lib/decorators/Otel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,IAAI,CAAC,OAAyB;IAC5C,OAAO,UAAiD,MAAS;QAC/D,mBAAmB,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAE/D,OAAO,aAAa,EAAE,CAAC,MAAM,CAAM,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Otel } from './Otel';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AsenaMiddlewareService } from '@asenajs/asena/middleware';
|
|
2
|
+
import type { AsenaContext } from '@asenajs/asena/adapter';
|
|
3
|
+
export declare class OtelTracingMiddleware extends AsenaMiddlewareService {
|
|
4
|
+
private tracer;
|
|
5
|
+
private meter;
|
|
6
|
+
private requestCounter;
|
|
7
|
+
private requestDuration;
|
|
8
|
+
onInit(): void;
|
|
9
|
+
handle(ctx: AsenaContext<any, any>, next: () => Promise<void>): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { trace, metrics } from '@opentelemetry/api';
|
|
11
|
+
import { context as otelContext, propagation, SpanKind, SpanStatusCode, } from '@opentelemetry/api';
|
|
12
|
+
import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_URL_PATH, } from '@opentelemetry/semantic-conventions';
|
|
13
|
+
import { AsenaMiddlewareService } from '@asenajs/asena/middleware';
|
|
14
|
+
import { Middleware } from '@asenajs/asena/decorators';
|
|
15
|
+
import { PostConstruct } from '@asenajs/asena/decorators/ioc';
|
|
16
|
+
import { isRouteIgnored } from '../shared/OtelRuntimeConfig';
|
|
17
|
+
const LIBRARY_NAME = '@asenajs/asena-otel';
|
|
18
|
+
let OtelTracingMiddleware = class OtelTracingMiddleware extends AsenaMiddlewareService {
|
|
19
|
+
tracer;
|
|
20
|
+
meter;
|
|
21
|
+
requestCounter;
|
|
22
|
+
requestDuration;
|
|
23
|
+
onInit() {
|
|
24
|
+
this.tracer = trace.getTracer(LIBRARY_NAME);
|
|
25
|
+
this.meter = metrics.getMeter(LIBRARY_NAME);
|
|
26
|
+
this.requestCounter = this.meter.createCounter('http.server.request.count', {
|
|
27
|
+
description: 'Total number of HTTP requests',
|
|
28
|
+
});
|
|
29
|
+
this.requestDuration = this.meter.createHistogram('http.server.request.duration', {
|
|
30
|
+
description: 'HTTP request duration in milliseconds',
|
|
31
|
+
unit: 'ms',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async handle(ctx, next) {
|
|
35
|
+
const startTime = performance.now();
|
|
36
|
+
const method = ctx.req?.method ?? 'UNKNOWN';
|
|
37
|
+
const url = ctx.req?.url ? new URL(ctx.req.url).pathname : '/';
|
|
38
|
+
// Check ignoreRoutes against static URL path (before route matching)
|
|
39
|
+
if (isRouteIgnored(url)) {
|
|
40
|
+
await next();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Extract W3C traceparent from incoming request headers
|
|
44
|
+
const parentContext = propagation.extract(otelContext.active(), ctx.headers);
|
|
45
|
+
await this.tracer.startActiveSpan(`${method} ${url}`, { kind: SpanKind.SERVER }, parentContext, async (span) => {
|
|
46
|
+
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, method);
|
|
47
|
+
span.setAttribute(ATTR_URL_PATH, url);
|
|
48
|
+
try {
|
|
49
|
+
await next();
|
|
50
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
54
|
+
span.recordException(error);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
const duration = performance.now() - startTime;
|
|
59
|
+
// Read routePattern AFTER next() — in Hono, route matching happens during next()
|
|
60
|
+
const raw = ctx.routePattern;
|
|
61
|
+
const trimmed = raw && raw.length > 1 && raw.endsWith('/') ? raw.slice(0, -1) : raw;
|
|
62
|
+
const routePattern = trimmed && trimmed !== '/*' ? trimmed : undefined;
|
|
63
|
+
if (routePattern) {
|
|
64
|
+
span.updateName(`${method} ${routePattern}`);
|
|
65
|
+
span.setAttribute(ATTR_HTTP_ROUTE, routePattern);
|
|
66
|
+
}
|
|
67
|
+
const metricRoute = routePattern || url;
|
|
68
|
+
const attributes = {
|
|
69
|
+
[ATTR_HTTP_REQUEST_METHOD]: method,
|
|
70
|
+
[ATTR_URL_PATH]: metricRoute,
|
|
71
|
+
};
|
|
72
|
+
const statusCode = ctx.res?.status;
|
|
73
|
+
if (statusCode !== undefined) {
|
|
74
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);
|
|
75
|
+
attributes[ATTR_HTTP_RESPONSE_STATUS_CODE] = statusCode;
|
|
76
|
+
}
|
|
77
|
+
this.requestCounter.add(1, attributes);
|
|
78
|
+
this.requestDuration.record(duration, attributes);
|
|
79
|
+
span.end();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
__decorate([
|
|
85
|
+
PostConstruct(),
|
|
86
|
+
__metadata("design:type", Function),
|
|
87
|
+
__metadata("design:paramtypes", []),
|
|
88
|
+
__metadata("design:returntype", void 0)
|
|
89
|
+
], OtelTracingMiddleware.prototype, "onInit", null);
|
|
90
|
+
OtelTracingMiddleware = __decorate([
|
|
91
|
+
Middleware()
|
|
92
|
+
], OtelTracingMiddleware);
|
|
93
|
+
export { OtelTracingMiddleware };
|
|
94
|
+
//# sourceMappingURL=OtelTracingMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelTracingMiddleware.js","sourceRoot":"","sources":["../../../lib/middleware/OtelTracingMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
3
|
+
import type { ComponentPostProcessor } from '@asenajs/asena/ioc/types';
|
|
4
|
+
export declare class OtelTracingPostProcessor implements ComponentPostProcessor {
|
|
5
|
+
protected tracerProvider: BasicTracerProvider | null;
|
|
6
|
+
protected meterProvider: MeterProvider | null;
|
|
7
|
+
private tracer;
|
|
8
|
+
private autoTraceConfig;
|
|
9
|
+
onInit(): void;
|
|
10
|
+
postProcess<T>(instance: T, Class: any): T;
|
|
11
|
+
forceFlush(): Promise<void>;
|
|
12
|
+
shutdown(): Promise<void>;
|
|
13
|
+
private getOptions;
|
|
14
|
+
private setupContextManager;
|
|
15
|
+
private setupTracing;
|
|
16
|
+
private setupMetrics;
|
|
17
|
+
private registerShutdownHook;
|
|
18
|
+
private getComponentType;
|
|
19
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { trace, metrics, context, propagation, SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
11
|
+
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
12
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
13
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
14
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
15
|
+
import { BasicTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
16
|
+
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
17
|
+
import { getMetadata } from 'reflect-metadata/no-conflict';
|
|
18
|
+
import { PostConstruct } from '@asenajs/asena/decorators/ioc';
|
|
19
|
+
import { getOwnTypedMetadata } from '@asenajs/asena/utils';
|
|
20
|
+
import { OtelConstants } from '../constants/OtelConstants';
|
|
21
|
+
import { otelRuntimeConfig } from '../shared/OtelRuntimeConfig';
|
|
22
|
+
const COMPONENT_TYPE_SERVICE = 'SERVICE';
|
|
23
|
+
const COMPONENT_TYPE_CONTROLLER = 'CONTROLLER';
|
|
24
|
+
const LIBRARY_NAME = '@asenajs/asena-otel';
|
|
25
|
+
export class OtelTracingPostProcessor {
|
|
26
|
+
tracerProvider = null;
|
|
27
|
+
meterProvider = null;
|
|
28
|
+
tracer;
|
|
29
|
+
autoTraceConfig = {};
|
|
30
|
+
onInit() {
|
|
31
|
+
const options = this.getOptions();
|
|
32
|
+
if (!options)
|
|
33
|
+
return;
|
|
34
|
+
const resource = resourceFromAttributes({
|
|
35
|
+
[ATTR_SERVICE_NAME]: options.serviceName,
|
|
36
|
+
[ATTR_SERVICE_VERSION]: options.serviceVersion ?? '0.0.0',
|
|
37
|
+
});
|
|
38
|
+
this.setupContextManager();
|
|
39
|
+
this.setupTracing(resource, options.traceExporter, options.sampler);
|
|
40
|
+
if (options.metricReader) {
|
|
41
|
+
this.setupMetrics(resource, options.metricReader);
|
|
42
|
+
}
|
|
43
|
+
this.autoTraceConfig = options.autoTrace ?? {};
|
|
44
|
+
otelRuntimeConfig.ignoreRoutes = options.ignoreRoutes ?? [];
|
|
45
|
+
this.tracer = trace.getTracer(LIBRARY_NAME);
|
|
46
|
+
this.registerShutdownHook();
|
|
47
|
+
}
|
|
48
|
+
postProcess(instance, Class) {
|
|
49
|
+
const config = this.autoTraceConfig;
|
|
50
|
+
const componentType = this.getComponentType(Class);
|
|
51
|
+
if (!componentType)
|
|
52
|
+
return instance;
|
|
53
|
+
if (componentType === COMPONENT_TYPE_SERVICE && !config.services)
|
|
54
|
+
return instance;
|
|
55
|
+
if (componentType === COMPONENT_TYPE_CONTROLLER && !config.controllers)
|
|
56
|
+
return instance;
|
|
57
|
+
const className = Class.name;
|
|
58
|
+
const tracer = this.tracer;
|
|
59
|
+
return new Proxy(instance, {
|
|
60
|
+
get(target, prop, receiver) {
|
|
61
|
+
const value = Reflect.get(target, prop, receiver);
|
|
62
|
+
if (typeof value !== 'function')
|
|
63
|
+
return value;
|
|
64
|
+
if (prop === 'constructor' || typeof prop === 'symbol')
|
|
65
|
+
return value;
|
|
66
|
+
if (String(prop).startsWith('_'))
|
|
67
|
+
return value;
|
|
68
|
+
const spanName = `${className}.${String(prop)}`;
|
|
69
|
+
return function (...args) {
|
|
70
|
+
const span = tracer.startSpan(spanName, { kind: SpanKind.INTERNAL });
|
|
71
|
+
const spanContext = trace.setSpan(context.active(), span);
|
|
72
|
+
let result;
|
|
73
|
+
try {
|
|
74
|
+
result = context.with(spanContext, () => value.apply(target, args));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
78
|
+
span.recordException(error);
|
|
79
|
+
span.end();
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (result instanceof Promise) {
|
|
83
|
+
return result
|
|
84
|
+
.then((resolved) => {
|
|
85
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
86
|
+
span.end();
|
|
87
|
+
return resolved;
|
|
88
|
+
})
|
|
89
|
+
.catch((error) => {
|
|
90
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
91
|
+
span.recordException(error);
|
|
92
|
+
span.end();
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
97
|
+
span.end();
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async forceFlush() {
|
|
104
|
+
if (this.tracerProvider) {
|
|
105
|
+
await this.tracerProvider.forceFlush();
|
|
106
|
+
}
|
|
107
|
+
if (this.meterProvider) {
|
|
108
|
+
await this.meterProvider.forceFlush();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async shutdown() {
|
|
112
|
+
if (this.tracerProvider) {
|
|
113
|
+
await this.tracerProvider.shutdown();
|
|
114
|
+
this.tracerProvider = null;
|
|
115
|
+
}
|
|
116
|
+
if (this.meterProvider) {
|
|
117
|
+
await this.meterProvider.shutdown();
|
|
118
|
+
this.meterProvider = null;
|
|
119
|
+
}
|
|
120
|
+
context.disable();
|
|
121
|
+
}
|
|
122
|
+
getOptions() {
|
|
123
|
+
return getOwnTypedMetadata(OtelConstants.OptionsKey, this.constructor);
|
|
124
|
+
}
|
|
125
|
+
setupContextManager() {
|
|
126
|
+
const contextManager = new AsyncLocalStorageContextManager();
|
|
127
|
+
contextManager.enable();
|
|
128
|
+
context.setGlobalContextManager(contextManager);
|
|
129
|
+
propagation.setGlobalPropagator(new W3CTraceContextPropagator());
|
|
130
|
+
}
|
|
131
|
+
setupTracing(resource, exporter, sampler) {
|
|
132
|
+
this.tracerProvider = new BasicTracerProvider({
|
|
133
|
+
resource,
|
|
134
|
+
spanProcessors: [new BatchSpanProcessor(exporter)],
|
|
135
|
+
sampler,
|
|
136
|
+
});
|
|
137
|
+
trace.setGlobalTracerProvider(this.tracerProvider);
|
|
138
|
+
}
|
|
139
|
+
setupMetrics(resource, reader) {
|
|
140
|
+
this.meterProvider = new MeterProvider({ resource, readers: [reader] });
|
|
141
|
+
metrics.setGlobalMeterProvider(this.meterProvider);
|
|
142
|
+
}
|
|
143
|
+
registerShutdownHook() {
|
|
144
|
+
const shutdown = async () => {
|
|
145
|
+
await this.shutdown();
|
|
146
|
+
};
|
|
147
|
+
process.on('SIGTERM', shutdown);
|
|
148
|
+
process.on('SIGINT', shutdown);
|
|
149
|
+
}
|
|
150
|
+
getComponentType(Class) {
|
|
151
|
+
if (getMetadata(COMPONENT_TYPE_SERVICE, Class))
|
|
152
|
+
return COMPONENT_TYPE_SERVICE;
|
|
153
|
+
if (getMetadata(COMPONENT_TYPE_CONTROLLER, Class))
|
|
154
|
+
return COMPONENT_TYPE_CONTROLLER;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
__decorate([
|
|
159
|
+
PostConstruct(),
|
|
160
|
+
__metadata("design:type", Function),
|
|
161
|
+
__metadata("design:paramtypes", []),
|
|
162
|
+
__metadata("design:returntype", void 0)
|
|
163
|
+
], OtelTracingPostProcessor.prototype, "onInit", null);
|
|
164
|
+
//# sourceMappingURL=OtelTracingPostProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelTracingPostProcessor.js","sourceRoot":"","sources":["../../../lib/postprocessor/OtelTracingPostProcessor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Sampler } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a production-ready ratio-based sampler.
|
|
4
|
+
* Root spans are sampled at the given ratio; child spans respect the parent's decision.
|
|
5
|
+
*
|
|
6
|
+
* @param ratio - Fraction of traces to sample (0.0 to 1.0). Default 0.1 (10%).
|
|
7
|
+
*/
|
|
8
|
+
export declare function ratioBasedSampler(ratio?: number): Sampler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a production-ready ratio-based sampler.
|
|
4
|
+
* Root spans are sampled at the given ratio; child spans respect the parent's decision.
|
|
5
|
+
*
|
|
6
|
+
* @param ratio - Fraction of traces to sample (0.0 to 1.0). Default 0.1 (10%).
|
|
7
|
+
*/
|
|
8
|
+
export function ratioBasedSampler(ratio = 0.1) {
|
|
9
|
+
return new ParentBasedSampler({
|
|
10
|
+
root: new TraceIdRatioBasedSampler(ratio),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/samplers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAgB,MAAM,+BAA+B,CAAC;AAE3G;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAK,GAAG,GAAG;IAC3C,OAAO,IAAI,kBAAkB,CAAC;QAC5B,IAAI,EAAE,IAAI,wBAAwB,CAAC,KAAK,CAAC;KAC1C,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Tracer, type Meter, type Span } from '@opentelemetry/api';
|
|
2
|
+
export declare class OtelService {
|
|
3
|
+
private _tracer;
|
|
4
|
+
private _meter;
|
|
5
|
+
onInit(): void;
|
|
6
|
+
get tracer(): Tracer;
|
|
7
|
+
get meter(): Meter;
|
|
8
|
+
withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
|
|
9
|
+
getActiveSpan(): Span | undefined;
|
|
10
|
+
injectTraceContext(headers?: Record<string, string>): Record<string, string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { trace, metrics, context, propagation } from '@opentelemetry/api';
|
|
11
|
+
import { Service } from '@asenajs/asena/decorators';
|
|
12
|
+
import { PostConstruct } from '@asenajs/asena/decorators/ioc';
|
|
13
|
+
const LIBRARY_NAME = '@asenajs/asena-otel';
|
|
14
|
+
let OtelService = class OtelService {
|
|
15
|
+
_tracer;
|
|
16
|
+
_meter;
|
|
17
|
+
onInit() {
|
|
18
|
+
this._tracer = trace.getTracer(LIBRARY_NAME);
|
|
19
|
+
this._meter = metrics.getMeter(LIBRARY_NAME);
|
|
20
|
+
}
|
|
21
|
+
get tracer() {
|
|
22
|
+
return this._tracer;
|
|
23
|
+
}
|
|
24
|
+
get meter() {
|
|
25
|
+
return this._meter;
|
|
26
|
+
}
|
|
27
|
+
async withSpan(name, fn) {
|
|
28
|
+
return this._tracer.startActiveSpan(name, async (span) => {
|
|
29
|
+
try {
|
|
30
|
+
const result = await fn(span);
|
|
31
|
+
span.setStatus({ code: 1 }); // SpanStatusCode.OK
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
span.setStatus({ code: 2 }); // SpanStatusCode.ERROR
|
|
36
|
+
span.recordException(error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
span.end();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
getActiveSpan() {
|
|
45
|
+
return trace.getSpan(context.active());
|
|
46
|
+
}
|
|
47
|
+
injectTraceContext(headers = {}) {
|
|
48
|
+
propagation.inject(context.active(), headers);
|
|
49
|
+
return headers;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
__decorate([
|
|
53
|
+
PostConstruct(),
|
|
54
|
+
__metadata("design:type", Function),
|
|
55
|
+
__metadata("design:paramtypes", []),
|
|
56
|
+
__metadata("design:returntype", void 0)
|
|
57
|
+
], OtelService.prototype, "onInit", null);
|
|
58
|
+
OtelService = __decorate([
|
|
59
|
+
Service('OtelService')
|
|
60
|
+
], OtelService);
|
|
61
|
+
export { OtelService };
|
|
62
|
+
//# sourceMappingURL=OtelService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelService.js","sourceRoot":"","sources":["../../../lib/services/OtelService.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAsC,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAGpC,IAAM,WAAW,GAAjB,MAAM,WAAW;IAEd,OAAO,CAAU;IAEjB,MAAM,CAAS;IAGhB,MAAM;QACX,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAI,IAAY,EAAE,EAA8B;QACnE,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACvD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;gBAE9B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB;gBAEjD,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAuB;gBACpD,IAAI,CAAC,eAAe,CAAC,KAAc,CAAC,CAAC;gBAErC,MAAM,KAAK,CAAC;YACd,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,aAAa;QAClB,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAEM,kBAAkB,CAAC,UAAkC,EAAE;QAC5D,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QAE9C,OAAO,OAAO,CAAC;IACjB,CAAC;CAEF,CAAA;AA1CQ;IADN,aAAa,EAAE;;;;yCAIf;AAVU,WAAW;IADvB,OAAO,CAAC,aAAa,CAAC;GACV,WAAW,CAiDvB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const otelRuntimeConfig: {
|
|
2
|
+
ignoreRoutes: string[];
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Check if a route path should be excluded from tracing.
|
|
6
|
+
* Supports exact match and wildcard suffix (e.g., `/admin/*`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function isRouteIgnored(path: string): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const otelRuntimeConfig = {
|
|
2
|
+
ignoreRoutes: [],
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Check if a route path should be excluded from tracing.
|
|
6
|
+
* Supports exact match and wildcard suffix (e.g., `/admin/*`).
|
|
7
|
+
*/
|
|
8
|
+
export function isRouteIgnored(path) {
|
|
9
|
+
for (const route of otelRuntimeConfig.ignoreRoutes) {
|
|
10
|
+
if (route.endsWith('*')) {
|
|
11
|
+
if (path.startsWith(route.slice(0, -1)))
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
if (path === route)
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=OtelRuntimeConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtelRuntimeConfig.js","sourceRoot":"","sources":["../../../lib/shared/OtelRuntimeConfig.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,EAAc;CAC7B,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,YAAY,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,IAAI,IAAI,KAAK,KAAK;gBAAE,OAAO,IAAI,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AsenaOtelOptions, AutoTraceConfig } from '../OtelConfig';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/types/index.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asenajs/asena-otel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "LibirSoft",
|
|
5
|
+
"description": "OpenTelemetry integration for AsenaJS - auto-tracing, HTTP metrics, distributed tracing",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "index.ts",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/AsenaJs/asena-otel.git"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"test:watch": "bun test --watch",
|
|
22
|
+
"test:coverage": "bun test --coverage",
|
|
23
|
+
"build": "bun run clean && tsc",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"prepublishOnly": "bun run build",
|
|
26
|
+
"changeset": "changeset",
|
|
27
|
+
"version": "changeset version",
|
|
28
|
+
"release": "bun run build && changeset publish"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@asenajs/asena": "^0.7.1",
|
|
32
|
+
"@changesets/cli": "^2.30.0",
|
|
33
|
+
"@opentelemetry/api": "^1.9.1",
|
|
34
|
+
"@opentelemetry/resources": "^2.6.1",
|
|
35
|
+
"@opentelemetry/sdk-metrics": "^2.6.1",
|
|
36
|
+
"@opentelemetry/sdk-trace-base": "^2.6.1",
|
|
37
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
38
|
+
"@types/bun": "latest",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
40
|
+
"eslint": "^8.57.1",
|
|
41
|
+
"eslint-config-alloy": "^5.1.2",
|
|
42
|
+
"eslint-config-prettier": "^9.1.2",
|
|
43
|
+
"eslint-plugin-alloy": "^1.2.1",
|
|
44
|
+
"eslint-plugin-import": "^2.32.0",
|
|
45
|
+
"eslint-plugin-n": "^16.6.2",
|
|
46
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
47
|
+
"eslint-plugin-promise": "^6.6.0",
|
|
48
|
+
"prettier": "^3.8.2",
|
|
49
|
+
"reflect-metadata": "^0.2.2"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@asenajs/asena": "^0.7.0",
|
|
53
|
+
"@opentelemetry/api": "^1.9.0",
|
|
54
|
+
"@opentelemetry/resources": "^2.6.0",
|
|
55
|
+
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
56
|
+
"@opentelemetry/sdk-trace-base": "^2.6.0",
|
|
57
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
58
|
+
"@opentelemetry/context-async-hooks": "^2.6.0",
|
|
59
|
+
"reflect-metadata": "^0.2.2",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@opentelemetry/context-async-hooks": "^2.6.1"
|
|
64
|
+
}
|
|
65
|
+
}
|