@nexusts/tracing 0.7.2
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/README.md +47 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/trace.d.ts +36 -0
- package/dist/hono-instrumentation.d.ts +20 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +546 -0
- package/dist/index.js.map +14 -0
- package/dist/module.d.ts +44 -0
- package/dist/propagation.d.ts +52 -0
- package/dist/service.d.ts +94 -0
- package/dist/types.d.ts +89 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @nexusts/tracing
|
|
2
|
+
|
|
3
|
+
> **NexusTS** — Bun-native fullstack framework
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
OpenTelemetry distributed tracing.
|
|
8
|
+
|
|
9
|
+
Lazy-loads the OTel SDK. W3C + B3 propagation. Hono auto-instrumentation middleware. @Trace() method decorator. Exporter-agnostic.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
This module is part of the NexusTS monorepo. Each module is published as its own npm package under the `@nexusts/` scope.
|
|
14
|
+
|
|
15
|
+
Most apps start with just the core:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @nexusts/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then add this module only if you need it:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @nexusts/tracing
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Peer dependencies
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun add @opentelemetry/api
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **`@opentelemetry/api`** ^1.9.0 — OpenTelemetry API. Required to enable tracing.
|
|
34
|
+
|
|
35
|
+
Without them the module loads but its public methods throw a clear error pointing to this install command on first call.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { /* public API */ } from "@nexusts/tracing";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See the [user guide](../../docs/user-guide/tracing.md) and the [example app](../../examples/) for a working demo.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT — see the root [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Trace, getTraceOptions, type TraceOptions } from "./trace.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@Trace()` — class-method decorator that wraps the call in a span.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* class UserService {
|
|
6
|
+
* @Trace() // span name = class.method
|
|
7
|
+
* async findById(id: string) { ... }
|
|
8
|
+
*
|
|
9
|
+
* @Trace("user.lookup") // explicit span name
|
|
10
|
+
* async lookup(name: string) { ... }
|
|
11
|
+
*
|
|
12
|
+
* @Trace({ name: "user.cache.get", attributes: { cache: "lru" } })
|
|
13
|
+
* async getFromCache(key: string) { ... }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* The decorator reads the `TracingService` from the global registry
|
|
17
|
+
* (set by `TracingModule.forRoot()`). When no service is registered
|
|
18
|
+
* the decorator is a pass-through.
|
|
19
|
+
*
|
|
20
|
+
* Sync methods stay sync; async methods stay async. The decorator
|
|
21
|
+
* detects `AsyncFunction` and uses `withSpan` / `withSpanSync` accordingly.
|
|
22
|
+
*/
|
|
23
|
+
import type { SpanOptions } from "../types.js";
|
|
24
|
+
export type TraceOptions = string | undefined | (SpanOptions & {
|
|
25
|
+
name: string;
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Method decorator: wrap a call in a span.
|
|
29
|
+
*
|
|
30
|
+
* The decorator works on both async and sync methods. The wrapped
|
|
31
|
+
* function preserves `this` so decorators on classes still see the
|
|
32
|
+
* right instance.
|
|
33
|
+
*/
|
|
34
|
+
export declare function Trace(opts?: TraceOptions): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
35
|
+
/** Read the @Trace options for a given method (used by OpenAPI integration). */
|
|
36
|
+
export declare function getTraceOptions(target: object, method: string): TraceOptions | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono auto-instrumentation for `nexusjs/tracing`.
|
|
3
|
+
*
|
|
4
|
+
* Returns a Hono middleware that:
|
|
5
|
+
* 1. Extracts the incoming trace context (W3C `traceparent`).
|
|
6
|
+
* 2. Starts a `SERVER` span with HTTP method, route, target, etc.
|
|
7
|
+
* 3. Records the response status, body size, and any thrown error.
|
|
8
|
+
*
|
|
9
|
+
* The middleware is a no-op when the SDK is not configured: the
|
|
10
|
+
* OTel API returns a no-op span that immediately ends, but the
|
|
11
|
+
* `traceparent` is still parsed for the response.
|
|
12
|
+
*/
|
|
13
|
+
import type { MiddlewareHandler } from "hono";
|
|
14
|
+
import type { TracingService } from "./service.js";
|
|
15
|
+
export declare function tracingMiddleware(service: TracingService): MiddlewareHandler;
|
|
16
|
+
/**
|
|
17
|
+
* Inject the active trace context into an outgoing fetch / response.
|
|
18
|
+
* Use in fetch() calls so downstream services pick up the trace.
|
|
19
|
+
*/
|
|
20
|
+
export declare function injectOutgoingTraceparent(service: TracingService, headers?: Record<string, string>): Record<string, string>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nexusjs/tracing` — OpenTelemetry-based distributed tracing.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* - `TracingService` — the main service (lives in DI)
|
|
6
|
+
* - `TracingModule.forRoot()` — wires up the OTel SDK
|
|
7
|
+
* - `tracingMiddleware()` — Hono auto-instrumentation
|
|
8
|
+
* - `@Trace()` decorator — wrap a method in a span
|
|
9
|
+
* - `withSpan(name, fn)` — manual span helper
|
|
10
|
+
* - W3C + B3 propagation helpers
|
|
11
|
+
* - `InMemorySpanRecorder` — for tests / `console` exporter
|
|
12
|
+
*
|
|
13
|
+
* The OpenTelemetry **API** package is the only required
|
|
14
|
+
* dependency (~7kb). The **SDK** packages are optional peer
|
|
15
|
+
* dependencies — install them when you call `forRoot()`.
|
|
16
|
+
*
|
|
17
|
+
* bun add @opentelemetry/api # always
|
|
18
|
+
* bun add @opentelemetry/sdk-node # for OTLP export
|
|
19
|
+
* bun add @opentelemetry/exporter-trace-otlp-http
|
|
20
|
+
* bun add @opentelemetry/resources
|
|
21
|
+
* bun add @opentelemetry/semantic-conventions
|
|
22
|
+
*/
|
|
23
|
+
export { TracingService, TRACING_SERVICE_TOKEN, InMemorySpanRecorder, setTracingService, getTracingService, } from "./service.js";
|
|
24
|
+
export { TracingModule, TRACING_CONFIG_TOKEN } from "./module.js";
|
|
25
|
+
export { tracingMiddleware, injectOutgoingTraceparent, } from "./hono-instrumentation.js";
|
|
26
|
+
export { parseTraceParent, formatTraceParent, extractB3Context, inject as injectContextHeaders, extract as extractContextHeaders, TRACE_PARENT_HEADER, TRACE_STATE_HEADER, B3_TRACE_ID_HEADER, B3_SPAN_ID_HEADER, B3_SAMPLED_HEADER, type ParsedTraceParent, } from "./propagation.js";
|
|
27
|
+
export { Trace, getTraceOptions, type TraceOptions } from "./decorators/index.js";
|
|
28
|
+
export type { ActiveSpan, FinishedSpan, SpanContext, SpanOptions, SpanStatus, TracingConfig, TracingExporter, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
5
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else
|
|
7
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
8
|
+
if (d = decorators[i])
|
|
9
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
10
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
11
|
+
};
|
|
12
|
+
var __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
13
|
+
var __legacyMetadataTS = (k, v) => {
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
15
|
+
return Reflect.metadata(k, v);
|
|
16
|
+
};
|
|
17
|
+
var __require = import.meta.require;
|
|
18
|
+
|
|
19
|
+
// packages/tracing/src/service.ts
|
|
20
|
+
import {
|
|
21
|
+
context as otelContext,
|
|
22
|
+
propagation,
|
|
23
|
+
SpanStatusCode,
|
|
24
|
+
SpanKind,
|
|
25
|
+
trace
|
|
26
|
+
} from "@opentelemetry/api";
|
|
27
|
+
|
|
28
|
+
class InMemorySpanRecorder {
|
|
29
|
+
finished = [];
|
|
30
|
+
nextEventCounter = 0;
|
|
31
|
+
record(span) {
|
|
32
|
+
this.finished.push(span);
|
|
33
|
+
this.nextEventCounter++;
|
|
34
|
+
}
|
|
35
|
+
getAll() {
|
|
36
|
+
return this.finished;
|
|
37
|
+
}
|
|
38
|
+
findByName(name) {
|
|
39
|
+
return this.finished.filter((s) => s.name === name);
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.finished = [];
|
|
43
|
+
this.nextEventCounter = 0;
|
|
44
|
+
}
|
|
45
|
+
get size() {
|
|
46
|
+
return this.finished.length;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class OtelActiveSpan {
|
|
51
|
+
name;
|
|
52
|
+
traceId;
|
|
53
|
+
spanId;
|
|
54
|
+
isRecording;
|
|
55
|
+
otelSpan;
|
|
56
|
+
constructor(name, traceId, spanId, isRecording, otelSpan) {
|
|
57
|
+
this.name = name;
|
|
58
|
+
this.traceId = traceId;
|
|
59
|
+
this.spanId = spanId;
|
|
60
|
+
this.isRecording = isRecording;
|
|
61
|
+
this.otelSpan = otelSpan;
|
|
62
|
+
}
|
|
63
|
+
setAttribute(key, value) {
|
|
64
|
+
this.otelSpan.setAttribute(key, value);
|
|
65
|
+
}
|
|
66
|
+
setAttributes(attributes) {
|
|
67
|
+
this.otelSpan.setAttributes(attributes);
|
|
68
|
+
}
|
|
69
|
+
addEvent(name, attributes) {
|
|
70
|
+
this.otelSpan.addEvent(name, attributes);
|
|
71
|
+
}
|
|
72
|
+
recordException(err) {
|
|
73
|
+
if (err instanceof Error) {
|
|
74
|
+
this.otelSpan.recordException(err);
|
|
75
|
+
} else {
|
|
76
|
+
this.otelSpan.recordException(new Error(String(err)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
setStatus(status, description) {
|
|
80
|
+
const code = status === "ok" ? SpanStatusCode.OK : status === "error" ? SpanStatusCode.ERROR : SpanStatusCode.UNSET;
|
|
81
|
+
this.otelSpan.setStatus({ code, message: description });
|
|
82
|
+
}
|
|
83
|
+
end() {
|
|
84
|
+
this.otelSpan.end();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
var TRACING_SERVICE_TOKEN = Symbol.for("nexus:TracingService");
|
|
88
|
+
var _current;
|
|
89
|
+
function setTracingService(service) {
|
|
90
|
+
_current = service;
|
|
91
|
+
}
|
|
92
|
+
function getTracingService() {
|
|
93
|
+
return _current;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class TracingService {
|
|
97
|
+
tracer;
|
|
98
|
+
recorder = new InMemorySpanRecorder;
|
|
99
|
+
sdkStop;
|
|
100
|
+
initialized = false;
|
|
101
|
+
constructor() {
|
|
102
|
+
this.tracer = trace.getTracer("nexusjs", "0.4.0");
|
|
103
|
+
}
|
|
104
|
+
get isInitialized() {
|
|
105
|
+
return this.initialized;
|
|
106
|
+
}
|
|
107
|
+
getSpans() {
|
|
108
|
+
return this.recorder.getAll();
|
|
109
|
+
}
|
|
110
|
+
findSpans(name) {
|
|
111
|
+
return this.recorder.findByName(name);
|
|
112
|
+
}
|
|
113
|
+
clearSpans() {
|
|
114
|
+
this.recorder.clear();
|
|
115
|
+
}
|
|
116
|
+
startSpan(name, options = {}) {
|
|
117
|
+
const kind = toOtelKind(options.kind ?? "internal");
|
|
118
|
+
const otelSpan = this.tracer.startSpan(name, {
|
|
119
|
+
kind,
|
|
120
|
+
attributes: options.attributes,
|
|
121
|
+
startTime: options.startTime
|
|
122
|
+
});
|
|
123
|
+
const ctx = otelSpan.spanContext();
|
|
124
|
+
return new OtelActiveSpan(name, ctx.traceId, ctx.spanId, otelSpan.isRecording(), otelSpan);
|
|
125
|
+
}
|
|
126
|
+
async withSpan(name, fn, options = {}) {
|
|
127
|
+
const span = this.startSpan(name, options);
|
|
128
|
+
const ctx = trace.setSpan(otelContext.active(), span["otelSpan"]);
|
|
129
|
+
try {
|
|
130
|
+
const result = await otelContext.with(ctx, () => fn(span));
|
|
131
|
+
if (span.isRecording)
|
|
132
|
+
span.setStatus("ok");
|
|
133
|
+
return result;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (span.isRecording) {
|
|
136
|
+
span.recordException(err);
|
|
137
|
+
span.setStatus("error", err instanceof Error ? err.message : String(err));
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
} finally {
|
|
141
|
+
span.end();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
withSpanSync(name, fn, options = {}) {
|
|
145
|
+
const span = this.startSpan(name, options);
|
|
146
|
+
try {
|
|
147
|
+
const result = fn(span);
|
|
148
|
+
if (span.isRecording)
|
|
149
|
+
span.setStatus("ok");
|
|
150
|
+
return result;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (span.isRecording) {
|
|
153
|
+
span.recordException(err);
|
|
154
|
+
span.setStatus("error", err instanceof Error ? err.message : String(err));
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
} finally {
|
|
158
|
+
span.end();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
getCurrentTraceId() {
|
|
162
|
+
const ctx = trace.getSpan(otelContext.active())?.spanContext();
|
|
163
|
+
return ctx?.traceId;
|
|
164
|
+
}
|
|
165
|
+
getCurrentSpanId() {
|
|
166
|
+
const ctx = trace.getSpan(otelContext.active())?.spanContext();
|
|
167
|
+
return ctx?.spanId;
|
|
168
|
+
}
|
|
169
|
+
getCurrentContext() {
|
|
170
|
+
return otelContext.active();
|
|
171
|
+
}
|
|
172
|
+
extractContext(headers) {
|
|
173
|
+
const flat = flattenHeaders(headers);
|
|
174
|
+
return propagation.extract(otelContext.active(), flat);
|
|
175
|
+
}
|
|
176
|
+
injectContext(headers = {}) {
|
|
177
|
+
const out = { ...headers };
|
|
178
|
+
propagation.inject(otelContext.active(), out);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
async startSdk(config) {
|
|
182
|
+
if (this.initialized)
|
|
183
|
+
return;
|
|
184
|
+
const serviceName = config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? "nexusjs";
|
|
185
|
+
const endpoint = config.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318";
|
|
186
|
+
const sampleRatio = config.sampleRatio ?? 1;
|
|
187
|
+
let NodeSDK, OTLPTraceExporter, Resource, SemanticResourceAttributes;
|
|
188
|
+
try {
|
|
189
|
+
({ NodeSDK } = await import("@opentelemetry/sdk-node"));
|
|
190
|
+
({ OTLPTraceExporter } = await import("@opentelemetry/exporter-trace-otlp-http"));
|
|
191
|
+
({ Resource } = await import("@opentelemetry/resources"));
|
|
192
|
+
const semconv = await import("@opentelemetry/semantic-conventions");
|
|
193
|
+
SemanticResourceAttributes = semconv.SemanticResourceAttributes ?? semconv;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
throw new Error("TracingModule.forRoot() requires the OTel SDK packages. Install with: bun add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions");
|
|
196
|
+
}
|
|
197
|
+
const resourceAttrs = {
|
|
198
|
+
[SemanticResourceAttributes.SERVICE_NAME ?? "service.name"]: serviceName,
|
|
199
|
+
[SemanticResourceAttributes.SERVICE_VERSION ?? "service.version"]: config.serviceVersion ?? "0.0.0",
|
|
200
|
+
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT ?? "deployment.environment"]: config.environment ?? "development" ?? "development",
|
|
201
|
+
...config.resourceAttributes
|
|
202
|
+
};
|
|
203
|
+
const resource = new Resource(resourceAttrs);
|
|
204
|
+
let traceExporter;
|
|
205
|
+
if (config.exporter === "console" || config.exporter === "memory" || !config.exporter) {
|
|
206
|
+
traceExporter = undefined;
|
|
207
|
+
} else {
|
|
208
|
+
traceExporter = new OTLPTraceExporter({ url: `${endpoint.replace(/\/$/, "")}/v1/traces` });
|
|
209
|
+
}
|
|
210
|
+
const sdk = new NodeSDK({
|
|
211
|
+
resource,
|
|
212
|
+
traceExporter,
|
|
213
|
+
sampler: {
|
|
214
|
+
shouldSample: () => ({
|
|
215
|
+
decision: Math.random() < sampleRatio ? 1 : 0
|
|
216
|
+
}),
|
|
217
|
+
toString: () => `RatioSampler(${sampleRatio})`
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
sdk.start();
|
|
221
|
+
this.sdkStop = async () => {
|
|
222
|
+
try {
|
|
223
|
+
await sdk.shutdown();
|
|
224
|
+
} catch {}
|
|
225
|
+
};
|
|
226
|
+
this.initialized = true;
|
|
227
|
+
}
|
|
228
|
+
async stopSdk() {
|
|
229
|
+
if (this.sdkStop) {
|
|
230
|
+
await this.sdkStop();
|
|
231
|
+
this.sdkStop = undefined;
|
|
232
|
+
}
|
|
233
|
+
this.initialized = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function toOtelKind(kind) {
|
|
237
|
+
switch (kind) {
|
|
238
|
+
case "server":
|
|
239
|
+
return SpanKind.SERVER;
|
|
240
|
+
case "client":
|
|
241
|
+
return SpanKind.CLIENT;
|
|
242
|
+
case "producer":
|
|
243
|
+
return SpanKind.PRODUCER;
|
|
244
|
+
case "consumer":
|
|
245
|
+
return SpanKind.CONSUMER;
|
|
246
|
+
default:
|
|
247
|
+
return SpanKind.INTERNAL;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function flattenHeaders(headers) {
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
253
|
+
if (v === undefined)
|
|
254
|
+
continue;
|
|
255
|
+
out[k.toLowerCase()] = Array.isArray(v) ? v.join(",") : v;
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
// packages/tracing/src/module.ts
|
|
260
|
+
import { Inject, Injectable, Module } from "@nexusts/core";
|
|
261
|
+
|
|
262
|
+
// packages/tracing/src/hono-instrumentation.ts
|
|
263
|
+
import { SpanKind as SpanKind2, SpanStatusCode as SpanStatusCode2, trace as trace2 } from "@opentelemetry/api";
|
|
264
|
+
function tracingMiddleware(service) {
|
|
265
|
+
return async (c, next) => {
|
|
266
|
+
const incoming = c.req.raw.headers;
|
|
267
|
+
const flat = {};
|
|
268
|
+
incoming.forEach((v, k) => flat[k] = v);
|
|
269
|
+
const extractedCtx = service.extractContext(flat);
|
|
270
|
+
const method = c.req.method;
|
|
271
|
+
const path = c.req.path;
|
|
272
|
+
const route = c.req.routePath ?? path;
|
|
273
|
+
const userAgent = c.req.header("user-agent") ?? "";
|
|
274
|
+
const url = new URL(c.req.url);
|
|
275
|
+
const clientIp = c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip") ?? "";
|
|
276
|
+
const span = service.tracer.startSpan(`HTTP ${method} ${route}`, {
|
|
277
|
+
kind: SpanKind2.SERVER,
|
|
278
|
+
attributes: {
|
|
279
|
+
"http.method": method,
|
|
280
|
+
"http.target": path,
|
|
281
|
+
"http.route": route,
|
|
282
|
+
"http.scheme": url.protocol.replace(":", ""),
|
|
283
|
+
"http.host": url.host,
|
|
284
|
+
"http.user_agent": userAgent,
|
|
285
|
+
"http.client_ip": clientIp,
|
|
286
|
+
"url.path": path
|
|
287
|
+
}
|
|
288
|
+
}, extractedCtx);
|
|
289
|
+
const ctxWithSpan = trace2.setSpan(extractedCtx, span);
|
|
290
|
+
const result = await service.tracer.startActiveSpan(`HTTP ${method} ${route} handler`, { kind: SpanKind2.SERVER }, ctxWithSpan, async () => {
|
|
291
|
+
try {
|
|
292
|
+
await next();
|
|
293
|
+
const status = c.res.status;
|
|
294
|
+
span.setAttribute("http.status_code", status);
|
|
295
|
+
if (status >= 500) {
|
|
296
|
+
span.setStatus({ code: SpanStatusCode2.ERROR });
|
|
297
|
+
} else {
|
|
298
|
+
span.setStatus({ code: SpanStatusCode2.OK });
|
|
299
|
+
}
|
|
300
|
+
} catch (err) {
|
|
301
|
+
span.recordException(err);
|
|
302
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: err.message });
|
|
303
|
+
span.setAttribute("http.status_code", 500);
|
|
304
|
+
throw err;
|
|
305
|
+
} finally {
|
|
306
|
+
span.end();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return result;
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function injectOutgoingTraceparent(service, headers = {}) {
|
|
313
|
+
return service.injectContext(headers);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// packages/tracing/src/module.ts
|
|
317
|
+
var TRACING_CONFIG_TOKEN = Symbol.for("nexus:TracingConfig");
|
|
318
|
+
|
|
319
|
+
class TracingConfigHolder {
|
|
320
|
+
config;
|
|
321
|
+
constructor(config) {
|
|
322
|
+
this.config = config;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
TracingConfigHolder = __legacyDecorateClassTS([
|
|
326
|
+
Injectable(),
|
|
327
|
+
__legacyDecorateParamTS(0, Inject(TRACING_CONFIG_TOKEN)),
|
|
328
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
329
|
+
typeof Required === "undefined" ? Object : Required
|
|
330
|
+
])
|
|
331
|
+
], TracingConfigHolder);
|
|
332
|
+
|
|
333
|
+
class TracingServiceWithLifecycle extends TracingService {
|
|
334
|
+
async onApplicationBootstrap() {}
|
|
335
|
+
async onApplicationShutdown() {
|
|
336
|
+
await this.stopSdk();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
TracingServiceWithLifecycle = __legacyDecorateClassTS([
|
|
340
|
+
Injectable()
|
|
341
|
+
], TracingServiceWithLifecycle);
|
|
342
|
+
|
|
343
|
+
class TracingModule {
|
|
344
|
+
static forRoot(config = {}) {
|
|
345
|
+
const fullConfig = {
|
|
346
|
+
serviceName: config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? "nexusjs",
|
|
347
|
+
serviceVersion: config.serviceVersion ?? "0.0.0",
|
|
348
|
+
environment: config.environment ?? "development" ?? "development",
|
|
349
|
+
exporter: config.exporter ?? "otlp-http",
|
|
350
|
+
endpoint: config.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318",
|
|
351
|
+
sampleRatio: config.sampleRatio ?? 1,
|
|
352
|
+
enableHttpInstrumentation: config.enableHttpInstrumentation ?? true,
|
|
353
|
+
enableDbInstrumentation: config.enableDbInstrumentation ?? true,
|
|
354
|
+
resourceAttributes: config.resourceAttributes ?? {},
|
|
355
|
+
throwOnError: config.throwOnError ?? false
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
class ConfiguredTracingModule {
|
|
359
|
+
svc;
|
|
360
|
+
constructor(svc) {
|
|
361
|
+
this.svc = svc;
|
|
362
|
+
svc.startSdk(fullConfig);
|
|
363
|
+
setTracingService(svc);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
ConfiguredTracingModule = __legacyDecorateClassTS([
|
|
367
|
+
Module({
|
|
368
|
+
providers: [
|
|
369
|
+
TracingServiceWithLifecycle,
|
|
370
|
+
{ provide: TRACING_CONFIG_TOKEN, useValue: fullConfig },
|
|
371
|
+
{ provide: TRACING_SERVICE_TOKEN, useExisting: TracingServiceWithLifecycle }
|
|
372
|
+
],
|
|
373
|
+
exports: [TracingServiceWithLifecycle, TRACING_CONFIG_TOKEN, TRACING_SERVICE_TOKEN]
|
|
374
|
+
}),
|
|
375
|
+
__legacyDecorateParamTS(0, Inject(TracingServiceWithLifecycle)),
|
|
376
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
377
|
+
typeof TracingServiceWithLifecycle === "undefined" ? Object : TracingServiceWithLifecycle
|
|
378
|
+
])
|
|
379
|
+
], ConfiguredTracingModule);
|
|
380
|
+
Object.defineProperty(ConfiguredTracingModule, "name", { value: "ConfiguredTracingModule" });
|
|
381
|
+
const mod = ConfiguredTracingModule;
|
|
382
|
+
mod.middleware = () => {
|
|
383
|
+
const svc = new TracingService;
|
|
384
|
+
setTracingService(svc);
|
|
385
|
+
return tracingMiddleware(svc);
|
|
386
|
+
};
|
|
387
|
+
return mod;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
TracingModule = __legacyDecorateClassTS([
|
|
391
|
+
Module({
|
|
392
|
+
providers: [
|
|
393
|
+
TracingServiceWithLifecycle,
|
|
394
|
+
{ provide: TRACING_SERVICE_TOKEN, useExisting: TracingServiceWithLifecycle }
|
|
395
|
+
],
|
|
396
|
+
exports: [TracingServiceWithLifecycle, TRACING_SERVICE_TOKEN]
|
|
397
|
+
})
|
|
398
|
+
], TracingModule);
|
|
399
|
+
// packages/tracing/src/propagation.ts
|
|
400
|
+
import { propagation as propagation2, context } from "@opentelemetry/api";
|
|
401
|
+
var TRACE_PARENT_HEADER = "traceparent";
|
|
402
|
+
var TRACE_STATE_HEADER = "tracestate";
|
|
403
|
+
var B3_TRACE_ID_HEADER = "x-b3-traceid";
|
|
404
|
+
var B3_SPAN_ID_HEADER = "x-b3-spanid";
|
|
405
|
+
var B3_SAMPLED_HEADER = "x-b3-sampled";
|
|
406
|
+
function parseTraceParent(value) {
|
|
407
|
+
if (!value)
|
|
408
|
+
return;
|
|
409
|
+
const parts = value.trim().split("-");
|
|
410
|
+
if (parts.length !== 4)
|
|
411
|
+
return;
|
|
412
|
+
const [version, traceId, parentSpanId, flags] = parts;
|
|
413
|
+
if (!/^[0-9a-f]{2}$/i.test(version))
|
|
414
|
+
return;
|
|
415
|
+
if (!/^[0-9a-f]{32}$/i.test(traceId))
|
|
416
|
+
return;
|
|
417
|
+
if (traceId === "0".repeat(32))
|
|
418
|
+
return;
|
|
419
|
+
if (!/^[0-9a-f]{16}$/i.test(parentSpanId))
|
|
420
|
+
return;
|
|
421
|
+
if (parentSpanId === "0".repeat(16))
|
|
422
|
+
return;
|
|
423
|
+
if (!/^[0-9a-f]{2}$/i.test(flags))
|
|
424
|
+
return;
|
|
425
|
+
return {
|
|
426
|
+
version,
|
|
427
|
+
traceId: traceId.toLowerCase(),
|
|
428
|
+
parentSpanId: parentSpanId.toLowerCase(),
|
|
429
|
+
flags: flags.toLowerCase(),
|
|
430
|
+
sampled: (parseInt(flags, 16) & 1) === 1
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function formatTraceParent(traceId, spanId, sampled = true) {
|
|
434
|
+
const flags = sampled ? "01" : "00";
|
|
435
|
+
return `00-${traceId.toLowerCase()}-${spanId.toLowerCase()}-${flags}`;
|
|
436
|
+
}
|
|
437
|
+
function extractB3Context(headers) {
|
|
438
|
+
const b3 = headers["b3"] ?? headers["x-b3-traceid"];
|
|
439
|
+
if (!b3)
|
|
440
|
+
return;
|
|
441
|
+
if (b3.includes("-")) {
|
|
442
|
+
const [traceId, spanId, sampled] = b3.split("-");
|
|
443
|
+
if (traceId && spanId) {
|
|
444
|
+
return { traceId, spanId, sampled: sampled === "1" };
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
const spanId = headers["x-b3-spanid"];
|
|
448
|
+
const sampled = headers["x-b3-sampled"] === "1";
|
|
449
|
+
if (spanId)
|
|
450
|
+
return { traceId: b3, spanId, sampled };
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
function inject(headers) {
|
|
455
|
+
const out = { ...headers };
|
|
456
|
+
propagation2.inject(context.active(), out);
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
function extract(headers) {
|
|
460
|
+
return propagation2.extract(context.active(), headers);
|
|
461
|
+
}
|
|
462
|
+
// packages/tracing/src/decorators/trace.ts
|
|
463
|
+
var TRACE_KEY = Symbol.for("nexus:trace:options");
|
|
464
|
+
function Trace(opts) {
|
|
465
|
+
return function(target, propertyKey, descriptor) {
|
|
466
|
+
const original = descriptor.value;
|
|
467
|
+
if (typeof original !== "function") {
|
|
468
|
+
throw new Error(`@Trace() can only be applied to methods, got ${typeof original}`);
|
|
469
|
+
}
|
|
470
|
+
const options = normalizeOptions(opts ?? {}, target, propertyKey);
|
|
471
|
+
target[TRACE_KEY] = target[TRACE_KEY] ?? {};
|
|
472
|
+
target[TRACE_KEY][String(propertyKey)] = options;
|
|
473
|
+
const isAsync = original.constructor?.name === "AsyncFunction";
|
|
474
|
+
if (isAsync) {
|
|
475
|
+
descriptor.value = async function wrapped(...args) {
|
|
476
|
+
const service = getTracingService();
|
|
477
|
+
if (!service)
|
|
478
|
+
return original.apply(this, args);
|
|
479
|
+
return service.withSpan(options.name, async (span) => {
|
|
480
|
+
if (options.attributes)
|
|
481
|
+
span.setAttributes(options.attributes);
|
|
482
|
+
return original.apply(this, args);
|
|
483
|
+
}, { kind: options.kind ?? "internal", attributes: options.attributes });
|
|
484
|
+
};
|
|
485
|
+
} else {
|
|
486
|
+
descriptor.value = function wrapped(...args) {
|
|
487
|
+
const service = getTracingService();
|
|
488
|
+
if (!service)
|
|
489
|
+
return original.apply(this, args);
|
|
490
|
+
return service.withSpanSync(options.name, (span) => {
|
|
491
|
+
if (options.attributes)
|
|
492
|
+
span.setAttributes(options.attributes);
|
|
493
|
+
return original.apply(this, args);
|
|
494
|
+
}, { kind: options.kind ?? "internal", attributes: options.attributes });
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return descriptor;
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function getTraceOptions(target, method) {
|
|
501
|
+
return target[TRACE_KEY]?.[method];
|
|
502
|
+
}
|
|
503
|
+
function normalizeOptions(opts, target, propertyKey) {
|
|
504
|
+
if (typeof opts === "string") {
|
|
505
|
+
return { name: opts, kind: "internal" };
|
|
506
|
+
}
|
|
507
|
+
if (opts && typeof opts === "object" && "name" in opts && opts.name) {
|
|
508
|
+
const o = opts;
|
|
509
|
+
return {
|
|
510
|
+
name: o.name,
|
|
511
|
+
kind: o.kind ?? "internal",
|
|
512
|
+
attributes: o.attributes
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const cls = target?.constructor?.name ?? "Anonymous";
|
|
516
|
+
return {
|
|
517
|
+
name: `${cls}.${String(propertyKey)}`,
|
|
518
|
+
kind: "internal"
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
export {
|
|
522
|
+
tracingMiddleware,
|
|
523
|
+
setTracingService,
|
|
524
|
+
parseTraceParent,
|
|
525
|
+
injectOutgoingTraceparent,
|
|
526
|
+
inject as injectContextHeaders,
|
|
527
|
+
getTracingService,
|
|
528
|
+
getTraceOptions,
|
|
529
|
+
formatTraceParent,
|
|
530
|
+
extract as extractContextHeaders,
|
|
531
|
+
extractB3Context,
|
|
532
|
+
TracingService,
|
|
533
|
+
TracingModule,
|
|
534
|
+
Trace,
|
|
535
|
+
TRACING_SERVICE_TOKEN,
|
|
536
|
+
TRACING_CONFIG_TOKEN,
|
|
537
|
+
TRACE_STATE_HEADER,
|
|
538
|
+
TRACE_PARENT_HEADER,
|
|
539
|
+
InMemorySpanRecorder,
|
|
540
|
+
B3_TRACE_ID_HEADER,
|
|
541
|
+
B3_SPAN_ID_HEADER,
|
|
542
|
+
B3_SAMPLED_HEADER
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
//# debugId=283916FBDA13049264756E2164756E21
|
|
546
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/service.ts", "../src/module.ts", "../src/hono-instrumentation.ts", "../src/propagation.ts", "../src/decorators/trace.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `TracingService` — the framework's distributed-tracing primitive.\n *\n * Design:\n * 1. The OpenTelemetry API (`@opentelemetry/api`) is the only\n * required dependency; it's ~7kb and provides the no-op default\n * tracer when the SDK is not configured.\n * 2. The OTel SDK is **lazy-loaded**: only when the user calls\n * `TracingModule.forRoot(...)` is `@opentelemetry/sdk-node` and\n * the configured exporter imported. This keeps the bundle small\n * for users who don't trace.\n * 3. Without `forRoot()`, the service returns no-op spans. They have\n * valid `traceId` / `spanId` (the OTel no-op span id format) so\n * log lines and error reports don't need to special-case \"not\n * configured\".\n *\n * Public API:\n * - `startSpan(name, options?)` — create a new active span\n * - `withSpan(name, fn, options?)` — run `fn` inside a span, return its result\n * - `getCurrentTraceId()` / `getCurrentSpanId()` — read the active context\n * - `extractContext(headers)` / `injectContext(headers)` — W3C trace context\n * - `getSpans()` — read the in-memory span recorder (always available,\n * used for tests and for the `console` exporter)\n * - `reset()` — clear the in-memory recorder\n *\n * The service is registered in the DI container as a singleton. The\n * framework does **not** call `trace.getTracer` until something\n * actually starts a span.\n */\n\nimport {\n\tcontext as otelContext,\n\tpropagation,\n\tSpanStatusCode,\n\tSpanKind,\n\ttrace,\n\ttype Context,\n\ttype Span as OtelSpan,\n\ttype Tracer,\n} from \"@opentelemetry/api\";\nimport type {\n\tActiveSpan,\n\tFinishedSpan,\n\tSpanContext,\n\tSpanOptions,\n\tSpanStatus,\n} from \"./types.js\";\n\n/* ------------------------------------------------------------------ *\n * In-memory recorder\n * ------------------------------------------------------------------ */\n\nexport class InMemorySpanRecorder {\n\tprivate finished: FinishedSpan[] = [];\n\tprivate nextEventCounter = 0;\n\n\t/** Append a finished span. */\n\trecord(span: FinishedSpan): void {\n\t\tthis.finished.push(span);\n\t\tthis.nextEventCounter++;\n\t}\n\n\t/** Return all finished spans (most recent last). */\n\tgetAll(): FinishedSpan[] {\n\t\treturn this.finished;\n\t}\n\n\t/** Return only the spans whose `name` matches. */\n\tfindByName(name: string): FinishedSpan[] {\n\t\treturn this.finished.filter((s) => s.name === name);\n\t}\n\n\t/** Clear the recorder. */\n\tclear(): void {\n\t\tthis.finished = [];\n\t\tthis.nextEventCounter = 0;\n\t}\n\n\t/** Number of recorded spans. */\n\tget size(): number {\n\t\treturn this.finished.length;\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * ActiveSpan wrapper around OTel span\n * ------------------------------------------------------------------ */\n\nclass OtelActiveSpan implements ActiveSpan {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tpublic readonly traceId: string,\n\t\tpublic readonly spanId: string,\n\t\tpublic readonly isRecording: boolean,\n\t\tprivate readonly otelSpan: OtelSpan,\n\t) {}\n\n\tsetAttribute(key: string, value: string | number | boolean): void {\n\t\tthis.otelSpan.setAttribute(key, value);\n\t}\n\n\tsetAttributes(attributes: Record<string, string | number | boolean>): void {\n\t\tthis.otelSpan.setAttributes(attributes);\n\t}\n\n\taddEvent(name: string, attributes?: Record<string, unknown>): void {\n\t\tthis.otelSpan.addEvent(name, attributes as never);\n\t}\n\n\trecordException(err: unknown): void {\n\t\tif (err instanceof Error) {\n\t\t\tthis.otelSpan.recordException(err);\n\t\t} else {\n\t\t\tthis.otelSpan.recordException(new Error(String(err)));\n\t\t}\n\t}\n\n\tsetStatus(status: \"ok\" | \"error\" | \"unset\", description?: string): void {\n\t\tconst code =\n\t\t\tstatus === \"ok\"\n\t\t\t\t? SpanStatusCode.OK\n\t\t\t\t: status === \"error\"\n\t\t\t\t\t? SpanStatusCode.ERROR\n\t\t\t\t\t: SpanStatusCode.UNSET;\n\t\tthis.otelSpan.setStatus({ code, message: description });\n\t}\n\n\tend(): void {\n\t\tthis.otelSpan.end();\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * TracingService\n * ------------------------------------------------------------------ */\n\nexport const TRACING_SERVICE_TOKEN = Symbol.for(\"nexus:TracingService\");\n\n/**\n * Global registry of the active `TracingService` instance.\n * The framework's `TracingModule.forRoot()` calls `setTracingService()`.\n * Decorators that need a `TracingService` (e.g. `@Trace()`) call\n * `getTracingService()` to look it up without DI plumbing.\n */\nlet _current: TracingService | undefined;\n\nexport function setTracingService(service: TracingService): void {\n\t_current = service;\n}\n\nexport function getTracingService(): TracingService | undefined {\n\treturn _current;\n}\n\nexport class TracingService {\n\treadonly tracer: Tracer;\n\tprivate readonly recorder = new InMemorySpanRecorder();\n\tprivate sdkStop?: () => Promise<void>;\n\tprivate initialized = false;\n\n\tconstructor() {\n\t\t// Default OTel tracer: \"nexusjs\". Even with no SDK, this returns\n\t\t// a no-op tracer that produces no-op spans — never throws.\n\t\tthis.tracer = trace.getTracer(\"nexusjs\", \"0.4.0\");\n\t}\n\n\t/** True if the SDK has been started (i.e. `forRoot()` was called). */\n\tget isInitialized(): boolean {\n\t\treturn this.initialized;\n\t}\n\n\t/** Read all finished spans recorded so far. */\n\tgetSpans(): FinishedSpan[] {\n\t\treturn this.recorder.getAll();\n\t}\n\n\t/** Find spans by name. */\n\tfindSpans(name: string): FinishedSpan[] {\n\t\treturn this.recorder.findByName(name);\n\t}\n\n\t/** Clear the in-memory recorder (and the SDK's batch, if any). */\n\tclearSpans(): void {\n\t\tthis.recorder.clear();\n\t}\n\n\t/* ---------------- span lifecycle ---------------- */\n\n\tstartSpan(name: string, options: SpanOptions = {}): ActiveSpan {\n\t\tconst kind = toOtelKind(options.kind ?? \"internal\");\n\t\tconst otelSpan = this.tracer.startSpan(name, {\n\t\t\tkind,\n\t\t\tattributes: options.attributes as never,\n\t\t\tstartTime: options.startTime,\n\t\t});\n\n\t\tconst ctx = otelSpan.spanContext();\n\t\treturn new OtelActiveSpan(\n\t\t\tname,\n\t\t\tctx.traceId,\n\t\t\tctx.spanId,\n\t\t\totelSpan.isRecording(),\n\t\t\totelSpan,\n\t\t);\n\t}\n\n\t/** Run `fn` inside a new span. Returns the result of `fn`. */\n\tasync withSpan<T>(\n\t\tname: string,\n\t\tfn: (span: ActiveSpan) => Promise<T> | T,\n\t\toptions: SpanOptions = {},\n\t): Promise<T> {\n\t\tconst span = this.startSpan(name, options);\n\t\tconst ctx = trace.setSpan(otelContext.active(), (span as OtelActiveSpan)[\"otelSpan\"] as OtelSpan);\n\t\ttry {\n\t\t\tconst result = await otelContext.with(ctx, () => fn(span));\n\t\t\tif (span.isRecording) span.setStatus(\"ok\");\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tif (span.isRecording) {\n\t\t\t\tspan.recordException(err);\n\t\t\t\tspan.setStatus(\"error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t}\n\n\t/** Synchronous version of `withSpan`. */\n\twithSpanSync<T>(name: string, fn: (span: ActiveSpan) => T, options: SpanOptions = {}): T {\n\t\tconst span = this.startSpan(name, options);\n\t\ttry {\n\t\t\tconst result = fn(span);\n\t\t\tif (span.isRecording) span.setStatus(\"ok\");\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tif (span.isRecording) {\n\t\t\t\tspan.recordException(err);\n\t\t\t\tspan.setStatus(\"error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t}\n\n\t/* ---------------- context propagation ---------------- */\n\n\t/** Get the trace id of the active span, or `undefined`. */\n\tgetCurrentTraceId(): string | undefined {\n\t\tconst ctx = trace.getSpan(otelContext.active())?.spanContext();\n\t\treturn ctx?.traceId;\n\t}\n\n\t/** Get the span id of the active span, or `undefined`. */\n\tgetCurrentSpanId(): string | undefined {\n\t\tconst ctx = trace.getSpan(otelContext.active())?.spanContext();\n\t\treturn ctx?.spanId;\n\t}\n\n\t/** Get the current OTel `Context`. */\n\tgetCurrentContext(): Context {\n\t\treturn otelContext.active();\n\t}\n\n\t/**\n\t * Extract a span context from incoming HTTP headers.\n\t * Reads `traceparent` (W3C) and `x-b3-*` (B3 single) when present.\n\t * Returns `undefined` if no recognizable header is found.\n\t */\n\textractContext(headers: Record<string, string | string[] | undefined>): Context {\n\t\tconst flat = flattenHeaders(headers);\n\t\treturn propagation.extract(otelContext.active(), flat);\n\t}\n\n\t/**\n\t * Inject the active span context into outgoing HTTP headers.\n\t * Writes `traceparent` (W3C) by default.\n\t */\n\tinjectContext(headers: Record<string, string> = {}): Record<string, string> {\n\t\tconst out: Record<string, string> = { ...headers };\n\t\tpropagation.inject(otelContext.active(), out);\n\t\treturn out;\n\t}\n\n\t/* ---------------- SDK bootstrap (called by module) ---------------- */\n\n\t/**\n\t * Initialize the OpenTelemetry SDK with the given configuration.\n\t * This is called by `TracingModule.forRoot()`. It's idempotent.\n\t */\n\tasync startSdk(config: import(\"./types.js\").TracingConfig): Promise<void> {\n\t\tif (this.initialized) return;\n\n\t\tconst serviceName = config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? \"nexusjs\";\n\t\tconst endpoint = config.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? \"http://localhost:4318\";\n\t\tconst sampleRatio = config.sampleRatio ?? 1.0;\n\n\t\t// Lazy import the SDK so apps that don't use tracing don't pay the cost.\n\t\t// The SDK packages are optional peer dependencies; we resolve them\n\t\t// dynamically so users who don't trace don't need them.\n\t\tlet NodeSDK: any, OTLPTraceExporter: any, Resource: any, SemanticResourceAttributes: any;\n\t\ttry {\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ NodeSDK } = await import(\"@opentelemetry/sdk-node\"));\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ OTLPTraceExporter } = await import(\"@opentelemetry/exporter-trace-otlp-http\"));\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ Resource } = await import(\"@opentelemetry/resources\"));\n\t\t\tconst semconv = await import(\"@opentelemetry/semantic-conventions\");\n\t\t\tSemanticResourceAttributes = (semconv as any).SemanticResourceAttributes ?? semconv;\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t\"TracingModule.forRoot() requires the OTel SDK packages. \" +\n\t\t\t\t\t\"Install with: bun add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions\",\n\t\t\t);\n\t\t}\n\n\t\t// Build the resource\n\t\tconst resourceAttrs: Record<string, string> = {\n\t\t\t[SemanticResourceAttributes.SERVICE_NAME ?? \"service.name\"]: serviceName,\n\t\t\t[SemanticResourceAttributes.SERVICE_VERSION ?? \"service.version\"]:\n\t\t\t\tconfig.serviceVersion ?? \"0.0.0\",\n\t\t\t[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT ?? \"deployment.environment\"]:\n\t\t\t\tconfig.environment ?? process.env.NODE_ENV ?? \"development\",\n\t\t\t...config.resourceAttributes,\n\t\t};\n\t\tconst resource = new Resource(resourceAttrs);\n\n\t\t// Pick the exporter\n\t\tlet traceExporter: any;\n\t\tif (config.exporter === \"console\" || config.exporter === \"memory\" || !config.exporter) {\n\t\t\t// Console exporter: print to stdout. (Bun has no in-process\n\t\t\t// console exporter, so we use a simple OTel-compatible one\n\t\t\t// via the in-memory recorder.)\n\t\t\ttraceExporter = undefined; // We use the recorder for the in-memory case\n\t\t} else {\n\t\t\ttraceExporter = new OTLPTraceExporter({ url: `${endpoint.replace(/\\/$/, \"\")}/v1/traces` });\n\t\t}\n\n\t\t// Build the SDK\n\t\tconst sdk = new NodeSDK({\n\t\t\tresource,\n\t\t\ttraceExporter,\n\t\t\tsampler: {\n\t\t\t\t// Custom simple ratio sampler to avoid pulling the full sampler package.\n\t\t\t\tshouldSample: () => ({\n\t\t\t\t\tdecision: Math.random() < sampleRatio ? 1 : 0,\n\t\t\t\t}),\n\t\t\t\ttoString: () => `RatioSampler(${sampleRatio})`,\n\t\t\t},\n\t\t});\n\t\tsdk.start();\n\n\t\tthis.sdkStop = async () => {\n\t\t\ttry {\n\t\t\t\tawait sdk.shutdown();\n\t\t\t} catch {\n\t\t\t\t/* ignore shutdown errors */\n\t\t\t}\n\t\t};\n\n\t\tthis.initialized = true;\n\t}\n\n\t/** Stop the SDK. Called on process exit / app shutdown. */\n\tasync stopSdk(): Promise<void> {\n\t\tif (this.sdkStop) {\n\t\t\tawait this.sdkStop();\n\t\t\tthis.sdkStop = undefined;\n\t\t}\n\t\tthis.initialized = false;\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * Helpers\n * ------------------------------------------------------------------ */\n\nfunction toOtelKind(kind: NonNullable<SpanOptions[\"kind\"]>): SpanKind {\n\tswitch (kind) {\n\t\tcase \"server\":\n\t\t\treturn SpanKind.SERVER;\n\t\tcase \"client\":\n\t\t\treturn SpanKind.CLIENT;\n\t\tcase \"producer\":\n\t\t\treturn SpanKind.PRODUCER;\n\t\tcase \"consumer\":\n\t\t\treturn SpanKind.CONSUMER;\n\t\tdefault:\n\t\t\treturn SpanKind.INTERNAL;\n\t}\n}\n\nfunction flattenHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const [k, v] of Object.entries(headers)) {\n\t\tif (v === undefined) continue;\n\t\tout[k.toLowerCase()] = Array.isArray(v) ? v.join(\",\") : v;\n\t}\n\treturn out;\n}\n\n/** Re-export for downstream type users. */\nexport type { SpanContext, SpanStatus };\n",
|
|
6
|
+
"/**\n * `TracingModule` — wires up `TracingService` into the DI container\n * and (optionally) installs the Hono auto-instrumentation middleware.\n *\n * Usage:\n * @Module({\n * imports: [TracingModule.forRoot({\n * serviceName: \"my-app\",\n * exporter: \"otlp-http\",\n * endpoint: \"http://otel-collector:4318\",\n * sampleRatio: 0.1,\n * })],\n * })\n * class AppModule {}\n *\n * When `forRoot()` is called:\n * 1. The OTel SDK is started (lazy `import()` of the SDK packages).\n * 2. The Hono `tracingMiddleware` is installed in the framework's\n * HTTP server (when `enableHttpInstrumentation !== false`).\n * 3. The `TracingService` becomes the active global service that\n * `@Trace()` decorators read from.\n *\n * When `forRoot()` is **not** called, `nexusjs/tracing` is a no-op:\n * - `TracingService` instances use OTel's default no-op tracer.\n * - `@Trace()` returns the original method unchanged.\n * - No SDK packages are loaded.\n */\n\nimport { Inject, Injectable, Module } from \"@nexusts/core\";\nimport type { MiddlewareHandler } from \"hono\";\nimport { TracingService, TRACING_SERVICE_TOKEN, setTracingService } from \"./service.js\";\nimport { tracingMiddleware } from \"./hono-instrumentation.js\";\nimport type { TracingConfig } from \"./types.js\";\n\nexport const TRACING_CONFIG_TOKEN = Symbol.for(\"nexus:TracingConfig\");\n\n@Injectable()\nexport class TracingConfigHolder {\n\tconstructor(@Inject(TRACING_CONFIG_TOKEN) public readonly config: Required<TracingConfig>) {}\n}\n\n@Injectable()\nexport class TracingServiceWithLifecycle extends TracingService {\n\tasync onApplicationBootstrap(): Promise<void> {\n\t\t// Will be set in the module factory; no-op here.\n\t}\n\n\tasync onApplicationShutdown(): Promise<void> {\n\t\tawait this.stopSdk();\n\t}\n}\n\n@Module({\n\tproviders: [\n\t\tTracingServiceWithLifecycle,\n\t\t{ provide: TRACING_SERVICE_TOKEN, useExisting: TracingServiceWithLifecycle },\n\t],\n\texports: [TracingServiceWithLifecycle, TRACING_SERVICE_TOKEN],\n})\nexport class TracingModule {\n\tstatic forRoot(config: TracingConfig = {}) {\n\t\tconst fullConfig: Required<TracingConfig> = {\n\t\t\tserviceName: config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? \"nexusjs\",\n\t\t\tserviceVersion: config.serviceVersion ?? \"0.0.0\",\n\t\t\tenvironment: config.environment ?? process.env.NODE_ENV ?? \"development\",\n\t\t\texporter: config.exporter ?? \"otlp-http\",\n\t\t\tendpoint: config.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? \"http://localhost:4318\",\n\t\t\tsampleRatio: config.sampleRatio ?? 1.0,\n\t\t\tenableHttpInstrumentation: config.enableHttpInstrumentation ?? true,\n\t\t\tenableDbInstrumentation: config.enableDbInstrumentation ?? true,\n\t\t\tresourceAttributes: config.resourceAttributes ?? {},\n\t\t\tthrowOnError: config.throwOnError ?? false,\n\t\t};\n\n\t\t@Module({\n\t\t\tproviders: [\n\t\t\t\tTracingServiceWithLifecycle,\n\t\t\t\t{ provide: TRACING_CONFIG_TOKEN, useValue: fullConfig },\n\t\t\t\t{ provide: TRACING_SERVICE_TOKEN, useExisting: TracingServiceWithLifecycle },\n\t\t\t],\n\t\t\texports: [TracingServiceWithLifecycle, TRACING_CONFIG_TOKEN, TRACING_SERVICE_TOKEN],\n\t\t})\n\t\tclass ConfiguredTracingModule {\n\t\t\tconstructor(@Inject(TracingServiceWithLifecycle) private svc: TracingServiceWithLifecycle) {\n\t\t\t\t// Side-effect: start the SDK & register the global.\n\t\t\t\tvoid svc.startSdk(fullConfig);\n\t\t\t\tsetTracingService(svc);\n\t\t\t}\n\t\t}\n\t\tObject.defineProperty(ConfiguredTracingModule, \"name\", { value: \"ConfiguredTracingModule\" });\n\n\t\t// Return both the module class and a helper for installing the\n\t\t// Hono middleware (for users who want to use `nexusjs/tracing` with\n\t\t// a custom Hono app, not the framework's HTTP server).\n\t\tconst mod = ConfiguredTracingModule as unknown as Function & {\n\t\t\tmiddleware: () => MiddlewareHandler;\n\t\t};\n\t\tmod.middleware = () => {\n\t\t\tconst svc = new TracingService();\n\t\t\tsetTracingService(svc);\n\t\t\treturn tracingMiddleware(svc);\n\t\t};\n\n\t\treturn mod;\n\t}\n}\n",
|
|
7
|
+
"/**\n * Hono auto-instrumentation for `nexusjs/tracing`.\n *\n * Returns a Hono middleware that:\n * 1. Extracts the incoming trace context (W3C `traceparent`).\n * 2. Starts a `SERVER` span with HTTP method, route, target, etc.\n * 3. Records the response status, body size, and any thrown error.\n *\n * The middleware is a no-op when the SDK is not configured: the\n * OTel API returns a no-op span that immediately ends, but the\n * `traceparent` is still parsed for the response.\n */\n\nimport { SpanKind, SpanStatusCode, trace, type Context } from \"@opentelemetry/api\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { TracingService } from \"./service.js\";\n\nexport function tracingMiddleware(service: TracingService): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\tconst incoming = c.req.raw.headers;\n\t\tconst flat: Record<string, string> = {};\n\t\tincoming.forEach((v, k) => (flat[k] = v));\n\t\tconst extractedCtx: Context = service.extractContext(flat);\n\n\t\tconst method = c.req.method;\n\t\tconst path = c.req.path;\n\t\tconst route = c.req.routePath ?? path;\n\t\tconst userAgent = c.req.header(\"user-agent\") ?? \"\";\n\t\tconst url = new URL(c.req.url);\n\t\tconst clientIp = c.req.header(\"x-forwarded-for\") ?? c.req.header(\"x-real-ip\") ?? \"\";\n\n\t\tconst span = service.tracer.startSpan(\n\t\t\t`HTTP ${method} ${route}`,\n\t\t\t{\n\t\t\t\tkind: SpanKind.SERVER,\n\t\t\t\tattributes: {\n\t\t\t\t\t\"http.method\": method,\n\t\t\t\t\t\"http.target\": path,\n\t\t\t\t\t\"http.route\": route,\n\t\t\t\t\t\"http.scheme\": url.protocol.replace(\":\", \"\"),\n\t\t\t\t\t\"http.host\": url.host,\n\t\t\t\t\t\"http.user_agent\": userAgent,\n\t\t\t\t\t\"http.client_ip\": clientIp,\n\t\t\t\t\t\"url.path\": path,\n\t\t\t\t},\n\t\t\t},\n\t\t\textractedCtx,\n\t\t);\n\n\t\tconst ctxWithSpan = trace.setSpan(extractedCtx, span);\n\t\tconst result = await service.tracer.startActiveSpan(\n\t\t\t`HTTP ${method} ${route} handler`,\n\t\t\t{ kind: SpanKind.SERVER },\n\t\t\tctxWithSpan,\n\t\t\tasync () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait next();\n\t\t\t\t\tconst status = c.res.status;\n\t\t\t\t\tspan.setAttribute(\"http.status_code\", status);\n\t\t\t\t\tif (status >= 500) {\n\t\t\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tspan.recordException(err as Error);\n\t\t\t\t\tspan.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });\n\t\t\t\t\tspan.setAttribute(\"http.status_code\", 500);\n\t\t\t\t\tthrow err;\n\t\t\t\t} finally {\n\t\t\t\t\tspan.end();\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t\treturn result as never;\n\t};\n}\n\n/**\n * Inject the active trace context into an outgoing fetch / response.\n * Use in fetch() calls so downstream services pick up the trace.\n */\nexport function injectOutgoingTraceparent(\n\tservice: TracingService,\n\theaders: Record<string, string> = {},\n): Record<string, string> {\n\treturn service.injectContext(headers);\n}\n",
|
|
8
|
+
"/**\n * W3C Trace Context propagation.\n *\n * The OTel API ships a default W3C propagator, so most use cases\n * don't need anything here. This file provides:\n * 1. Constants for header names.\n * 2. Helpers to format / parse a `traceparent` value.\n * 3. A simple B3 single-header propagator for legacy systems.\n *\n * Format reference: https://www.w3.org/TR/trace-context/#traceparent-header\n */\n\nimport { propagation, context, type Context } from \"@opentelemetry/api\";\n\n/* ------------------------------------------------------------------ *\n * Header constants\n * ------------------------------------------------------------------ */\n\nexport const TRACE_PARENT_HEADER = \"traceparent\";\nexport const TRACE_STATE_HEADER = \"tracestate\";\nexport const B3_TRACE_ID_HEADER = \"x-b3-traceid\";\nexport const B3_SPAN_ID_HEADER = \"x-b3-spanid\";\nexport const B3_SAMPLED_HEADER = \"x-b3-sampled\";\n\n/* ------------------------------------------------------------------ *\n * traceparent parser\n * ------------------------------------------------------------------ */\n\nexport interface ParsedTraceParent {\n\t/** \"00\" (version 0) for now. */\n\tversion: string;\n\t/** Full trace id (32 hex chars). */\n\ttraceId: string;\n\t/** Parent span id (16 hex chars). */\n\tparentSpanId: string;\n\t/** Trace flags byte, hex. */\n\tflags: string;\n\t/** True if the trace is sampled (flags bit 0 set). */\n\tsampled: boolean;\n}\n\n/**\n * Parse a `traceparent` header value.\n * Returns `undefined` if the value is malformed.\n */\nexport function parseTraceParent(value: string | undefined | null): ParsedTraceParent | undefined {\n\tif (!value) return undefined;\n\tconst parts = value.trim().split(\"-\");\n\tif (parts.length !== 4) return undefined;\n\tconst [version, traceId, parentSpanId, flags] = parts;\n\tif (!/^[0-9a-f]{2}$/i.test(version)) return undefined;\n\tif (!/^[0-9a-f]{32}$/i.test(traceId)) return undefined;\n\tif (traceId === \"0\".repeat(32)) return undefined;\n\tif (!/^[0-9a-f]{16}$/i.test(parentSpanId)) return undefined;\n\tif (parentSpanId === \"0\".repeat(16)) return undefined;\n\tif (!/^[0-9a-f]{2}$/i.test(flags)) return undefined;\n\treturn {\n\t\tversion,\n\t\ttraceId: traceId.toLowerCase(),\n\t\tparentSpanId: parentSpanId.toLowerCase(),\n\t\tflags: flags.toLowerCase(),\n\t\tsampled: (parseInt(flags, 16) & 0x01) === 0x01,\n\t};\n}\n\n/**\n * Format a `traceparent` value from a known context.\n * `00-<traceId>-<spanId>-<flags>`\n */\nexport function formatTraceParent(traceId: string, spanId: string, sampled = true): string {\n\tconst flags = sampled ? \"01\" : \"00\";\n\treturn `00-${traceId.toLowerCase()}-${spanId.toLowerCase()}-${flags}`;\n}\n\n/* ------------------------------------------------------------------ *\n * B3 single-header helpers (legacy Zipkin)\n * ------------------------------------------------------------------ */\n\n/**\n * Extract context from a B3 single-header (`b3: <traceId>-<spanId>-1`).\n * Falls through to W3C if no B3 header is present.\n */\nexport function extractB3Context(\n\theaders: Record<string, string>,\n): { traceId: string; spanId: string; sampled: boolean } | undefined {\n\tconst b3 = headers[\"b3\"] ?? headers[\"x-b3-traceid\"];\n\tif (!b3) return undefined;\n\tif (b3.includes(\"-\")) {\n\t\t// b3 single header: \"traceId-spanId-sampled-parentSpanId\"\n\t\tconst [traceId, spanId, sampled] = b3.split(\"-\");\n\t\tif (traceId && spanId) {\n\t\t\treturn { traceId, spanId, sampled: sampled === \"1\" };\n\t\t}\n\t} else {\n\t\t// x-b3-traceid + x-b3-spanid pair\n\t\tconst spanId = headers[\"x-b3-spanid\"];\n\t\tconst sampled = headers[\"x-b3-sampled\"] === \"1\";\n\t\tif (spanId) return { traceId: b3, spanId, sampled };\n\t}\n\treturn undefined;\n}\n\n/* ------------------------------------------------------------------ *\n * Re-export OTel's propagation API\n * ------------------------------------------------------------------ */\n\n/** Inject the active context into the given carrier. */\nexport function inject(headers: Record<string, string>): Record<string, string> {\n\tconst out: Record<string, string> = { ...headers };\n\tpropagation.inject(context.active(), out);\n\treturn out;\n}\n\n/** Extract a context from a header carrier. */\nexport function extract(headers: Record<string, string>): Context {\n\treturn propagation.extract(context.active(), headers);\n}\n",
|
|
9
|
+
"/**\n * `@Trace()` — class-method decorator that wraps the call in a span.\n *\n * Usage:\n * class UserService {\n * @Trace() // span name = class.method\n * async findById(id: string) { ... }\n *\n * @Trace(\"user.lookup\") // explicit span name\n * async lookup(name: string) { ... }\n *\n * @Trace({ name: \"user.cache.get\", attributes: { cache: \"lru\" } })\n * async getFromCache(key: string) { ... }\n * }\n *\n * The decorator reads the `TracingService` from the global registry\n * (set by `TracingModule.forRoot()`). When no service is registered\n * the decorator is a pass-through.\n *\n * Sync methods stay sync; async methods stay async. The decorator\n * detects `AsyncFunction` and uses `withSpan` / `withSpanSync` accordingly.\n */\n\nimport type { ActiveSpan, SpanOptions } from \"../types.js\";\nimport { getTracingService } from \"../service.js\";\n\nexport type TraceOptions =\n\t| string\n\t| undefined\n\t| (SpanOptions & { name: string });\n\nconst TRACE_KEY = Symbol.for(\"nexus:trace:options\");\n\n/**\n * Method decorator: wrap a call in a span.\n *\n * The decorator works on both async and sync methods. The wrapped\n * function preserves `this` so decorators on classes still see the\n * right instance.\n */\nexport function Trace(opts?: TraceOptions) {\n\treturn function (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) {\n\t\tconst original = descriptor.value as (...args: unknown[]) => unknown;\n\t\tif (typeof original !== \"function\") {\n\t\t\tthrow new Error(`@Trace() can only be applied to methods, got ${typeof original}`);\n\t\t}\n\n\t\tconst options = normalizeOptions(opts ?? {}, target, propertyKey);\n\t\t(target as any)[TRACE_KEY] = (target as any)[TRACE_KEY] ?? {};\n\t\t(target as any)[TRACE_KEY][String(propertyKey)] = options;\n\n\t\tconst isAsync = (original as any).constructor?.name === \"AsyncFunction\";\n\n\t\tif (isAsync) {\n\t\t\tdescriptor.value = async function wrapped(this: unknown, ...args: unknown[]) {\n\t\t\t\tconst service = getTracingService();\n\t\t\t\tif (!service) return original.apply(this, args);\n\t\t\t\treturn service.withSpan(\n\t\t\t\t\toptions.name,\n\t\t\t\t\tasync (span: ActiveSpan) => {\n\t\t\t\t\t\tif (options.attributes) span.setAttributes(options.attributes!);\n\t\t\t\t\t\treturn original.apply(this, args);\n\t\t\t\t\t},\n\t\t\t\t\t{ kind: options.kind ?? \"internal\", attributes: options.attributes },\n\t\t\t\t);\n\t\t\t};\n\t\t} else {\n\t\t\tdescriptor.value = function wrapped(this: unknown, ...args: unknown[]) {\n\t\t\t\tconst service = getTracingService();\n\t\t\t\tif (!service) return original.apply(this, args);\n\t\t\t\treturn service.withSpanSync(\n\t\t\t\t\toptions.name,\n\t\t\t\t\t(span: ActiveSpan) => {\n\t\t\t\t\t\tif (options.attributes) span.setAttributes(options.attributes!);\n\t\t\t\t\t\treturn original.apply(this, args);\n\t\t\t\t\t},\n\t\t\t\t\t{ kind: options.kind ?? \"internal\", attributes: options.attributes },\n\t\t\t\t);\n\t\t\t};\n\t\t}\n\t\treturn descriptor;\n\t};\n}\n\n/** Read the @Trace options for a given method (used by OpenAPI integration). */\nexport function getTraceOptions(target: object, method: string): TraceOptions | undefined {\n\treturn (target as any)[TRACE_KEY]?.[method];\n}\n\nfunction normalizeOptions(\n\topts: TraceOptions | object,\n\ttarget: object,\n\tpropertyKey: string | symbol,\n): { name: string; kind: NonNullable<SpanOptions[\"kind\"]>; attributes?: SpanOptions[\"attributes\"] } {\n\tif (typeof opts === \"string\") {\n\t\treturn { name: opts, kind: \"internal\" };\n\t}\n\tif (opts && typeof opts === \"object\" && \"name\" in opts && (opts as any).name) {\n\t\tconst o = opts as { name: string; kind?: SpanOptions[\"kind\"]; attributes?: SpanOptions[\"attributes\"] };\n\t\treturn {\n\t\t\tname: o.name,\n\t\t\tkind: o.kind ?? \"internal\",\n\t\t\tattributes: o.attributes,\n\t\t};\n\t}\n\t// Default: use \"ClassName.methodName\"\n\tconst cls = (target as any)?.constructor?.name ?? \"Anonymous\";\n\treturn {\n\t\tname: `${cls}.${String(propertyKey)}`,\n\t\tkind: \"internal\",\n\t};\n}\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "283916FBDA13049264756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `TracingModule` — wires up `TracingService` into the DI container
|
|
3
|
+
* and (optionally) installs the Hono auto-instrumentation middleware.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* @Module({
|
|
7
|
+
* imports: [TracingModule.forRoot({
|
|
8
|
+
* serviceName: "my-app",
|
|
9
|
+
* exporter: "otlp-http",
|
|
10
|
+
* endpoint: "http://otel-collector:4318",
|
|
11
|
+
* sampleRatio: 0.1,
|
|
12
|
+
* })],
|
|
13
|
+
* })
|
|
14
|
+
* class AppModule {}
|
|
15
|
+
*
|
|
16
|
+
* When `forRoot()` is called:
|
|
17
|
+
* 1. The OTel SDK is started (lazy `import()` of the SDK packages).
|
|
18
|
+
* 2. The Hono `tracingMiddleware` is installed in the framework's
|
|
19
|
+
* HTTP server (when `enableHttpInstrumentation !== false`).
|
|
20
|
+
* 3. The `TracingService` becomes the active global service that
|
|
21
|
+
* `@Trace()` decorators read from.
|
|
22
|
+
*
|
|
23
|
+
* When `forRoot()` is **not** called, `nexusjs/tracing` is a no-op:
|
|
24
|
+
* - `TracingService` instances use OTel's default no-op tracer.
|
|
25
|
+
* - `@Trace()` returns the original method unchanged.
|
|
26
|
+
* - No SDK packages are loaded.
|
|
27
|
+
*/
|
|
28
|
+
import type { MiddlewareHandler } from "hono";
|
|
29
|
+
import { TracingService } from "./service.js";
|
|
30
|
+
import type { TracingConfig } from "./types.js";
|
|
31
|
+
export declare const TRACING_CONFIG_TOKEN: unique symbol;
|
|
32
|
+
export declare class TracingConfigHolder {
|
|
33
|
+
readonly config: Required<TracingConfig>;
|
|
34
|
+
constructor(config: Required<TracingConfig>);
|
|
35
|
+
}
|
|
36
|
+
export declare class TracingServiceWithLifecycle extends TracingService {
|
|
37
|
+
onApplicationBootstrap(): Promise<void>;
|
|
38
|
+
onApplicationShutdown(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare class TracingModule {
|
|
41
|
+
static forRoot(config?: TracingConfig): Function & {
|
|
42
|
+
middleware: () => MiddlewareHandler;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context propagation.
|
|
3
|
+
*
|
|
4
|
+
* The OTel API ships a default W3C propagator, so most use cases
|
|
5
|
+
* don't need anything here. This file provides:
|
|
6
|
+
* 1. Constants for header names.
|
|
7
|
+
* 2. Helpers to format / parse a `traceparent` value.
|
|
8
|
+
* 3. A simple B3 single-header propagator for legacy systems.
|
|
9
|
+
*
|
|
10
|
+
* Format reference: https://www.w3.org/TR/trace-context/#traceparent-header
|
|
11
|
+
*/
|
|
12
|
+
import { type Context } from "@opentelemetry/api";
|
|
13
|
+
export declare const TRACE_PARENT_HEADER = "traceparent";
|
|
14
|
+
export declare const TRACE_STATE_HEADER = "tracestate";
|
|
15
|
+
export declare const B3_TRACE_ID_HEADER = "x-b3-traceid";
|
|
16
|
+
export declare const B3_SPAN_ID_HEADER = "x-b3-spanid";
|
|
17
|
+
export declare const B3_SAMPLED_HEADER = "x-b3-sampled";
|
|
18
|
+
export interface ParsedTraceParent {
|
|
19
|
+
/** "00" (version 0) for now. */
|
|
20
|
+
version: string;
|
|
21
|
+
/** Full trace id (32 hex chars). */
|
|
22
|
+
traceId: string;
|
|
23
|
+
/** Parent span id (16 hex chars). */
|
|
24
|
+
parentSpanId: string;
|
|
25
|
+
/** Trace flags byte, hex. */
|
|
26
|
+
flags: string;
|
|
27
|
+
/** True if the trace is sampled (flags bit 0 set). */
|
|
28
|
+
sampled: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse a `traceparent` header value.
|
|
32
|
+
* Returns `undefined` if the value is malformed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseTraceParent(value: string | undefined | null): ParsedTraceParent | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Format a `traceparent` value from a known context.
|
|
37
|
+
* `00-<traceId>-<spanId>-<flags>`
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatTraceParent(traceId: string, spanId: string, sampled?: boolean): string;
|
|
40
|
+
/**
|
|
41
|
+
* Extract context from a B3 single-header (`b3: <traceId>-<spanId>-1`).
|
|
42
|
+
* Falls through to W3C if no B3 header is present.
|
|
43
|
+
*/
|
|
44
|
+
export declare function extractB3Context(headers: Record<string, string>): {
|
|
45
|
+
traceId: string;
|
|
46
|
+
spanId: string;
|
|
47
|
+
sampled: boolean;
|
|
48
|
+
} | undefined;
|
|
49
|
+
/** Inject the active context into the given carrier. */
|
|
50
|
+
export declare function inject(headers: Record<string, string>): Record<string, string>;
|
|
51
|
+
/** Extract a context from a header carrier. */
|
|
52
|
+
export declare function extract(headers: Record<string, string>): Context;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `TracingService` — the framework's distributed-tracing primitive.
|
|
3
|
+
*
|
|
4
|
+
* Design:
|
|
5
|
+
* 1. The OpenTelemetry API (`@opentelemetry/api`) is the only
|
|
6
|
+
* required dependency; it's ~7kb and provides the no-op default
|
|
7
|
+
* tracer when the SDK is not configured.
|
|
8
|
+
* 2. The OTel SDK is **lazy-loaded**: only when the user calls
|
|
9
|
+
* `TracingModule.forRoot(...)` is `@opentelemetry/sdk-node` and
|
|
10
|
+
* the configured exporter imported. This keeps the bundle small
|
|
11
|
+
* for users who don't trace.
|
|
12
|
+
* 3. Without `forRoot()`, the service returns no-op spans. They have
|
|
13
|
+
* valid `traceId` / `spanId` (the OTel no-op span id format) so
|
|
14
|
+
* log lines and error reports don't need to special-case "not
|
|
15
|
+
* configured".
|
|
16
|
+
*
|
|
17
|
+
* Public API:
|
|
18
|
+
* - `startSpan(name, options?)` — create a new active span
|
|
19
|
+
* - `withSpan(name, fn, options?)` — run `fn` inside a span, return its result
|
|
20
|
+
* - `getCurrentTraceId()` / `getCurrentSpanId()` — read the active context
|
|
21
|
+
* - `extractContext(headers)` / `injectContext(headers)` — W3C trace context
|
|
22
|
+
* - `getSpans()` — read the in-memory span recorder (always available,
|
|
23
|
+
* used for tests and for the `console` exporter)
|
|
24
|
+
* - `reset()` — clear the in-memory recorder
|
|
25
|
+
*
|
|
26
|
+
* The service is registered in the DI container as a singleton. The
|
|
27
|
+
* framework does **not** call `trace.getTracer` until something
|
|
28
|
+
* actually starts a span.
|
|
29
|
+
*/
|
|
30
|
+
import { type Context, type Tracer } from "@opentelemetry/api";
|
|
31
|
+
import type { ActiveSpan, FinishedSpan, SpanContext, SpanOptions, SpanStatus } from "./types.js";
|
|
32
|
+
export declare class InMemorySpanRecorder {
|
|
33
|
+
private finished;
|
|
34
|
+
private nextEventCounter;
|
|
35
|
+
/** Append a finished span. */
|
|
36
|
+
record(span: FinishedSpan): void;
|
|
37
|
+
/** Return all finished spans (most recent last). */
|
|
38
|
+
getAll(): FinishedSpan[];
|
|
39
|
+
/** Return only the spans whose `name` matches. */
|
|
40
|
+
findByName(name: string): FinishedSpan[];
|
|
41
|
+
/** Clear the recorder. */
|
|
42
|
+
clear(): void;
|
|
43
|
+
/** Number of recorded spans. */
|
|
44
|
+
get size(): number;
|
|
45
|
+
}
|
|
46
|
+
export declare const TRACING_SERVICE_TOKEN: unique symbol;
|
|
47
|
+
export declare function setTracingService(service: TracingService): void;
|
|
48
|
+
export declare function getTracingService(): TracingService | undefined;
|
|
49
|
+
export declare class TracingService {
|
|
50
|
+
readonly tracer: Tracer;
|
|
51
|
+
private readonly recorder;
|
|
52
|
+
private sdkStop?;
|
|
53
|
+
private initialized;
|
|
54
|
+
constructor();
|
|
55
|
+
/** True if the SDK has been started (i.e. `forRoot()` was called). */
|
|
56
|
+
get isInitialized(): boolean;
|
|
57
|
+
/** Read all finished spans recorded so far. */
|
|
58
|
+
getSpans(): FinishedSpan[];
|
|
59
|
+
/** Find spans by name. */
|
|
60
|
+
findSpans(name: string): FinishedSpan[];
|
|
61
|
+
/** Clear the in-memory recorder (and the SDK's batch, if any). */
|
|
62
|
+
clearSpans(): void;
|
|
63
|
+
startSpan(name: string, options?: SpanOptions): ActiveSpan;
|
|
64
|
+
/** Run `fn` inside a new span. Returns the result of `fn`. */
|
|
65
|
+
withSpan<T>(name: string, fn: (span: ActiveSpan) => Promise<T> | T, options?: SpanOptions): Promise<T>;
|
|
66
|
+
/** Synchronous version of `withSpan`. */
|
|
67
|
+
withSpanSync<T>(name: string, fn: (span: ActiveSpan) => T, options?: SpanOptions): T;
|
|
68
|
+
/** Get the trace id of the active span, or `undefined`. */
|
|
69
|
+
getCurrentTraceId(): string | undefined;
|
|
70
|
+
/** Get the span id of the active span, or `undefined`. */
|
|
71
|
+
getCurrentSpanId(): string | undefined;
|
|
72
|
+
/** Get the current OTel `Context`. */
|
|
73
|
+
getCurrentContext(): Context;
|
|
74
|
+
/**
|
|
75
|
+
* Extract a span context from incoming HTTP headers.
|
|
76
|
+
* Reads `traceparent` (W3C) and `x-b3-*` (B3 single) when present.
|
|
77
|
+
* Returns `undefined` if no recognizable header is found.
|
|
78
|
+
*/
|
|
79
|
+
extractContext(headers: Record<string, string | string[] | undefined>): Context;
|
|
80
|
+
/**
|
|
81
|
+
* Inject the active span context into outgoing HTTP headers.
|
|
82
|
+
* Writes `traceparent` (W3C) by default.
|
|
83
|
+
*/
|
|
84
|
+
injectContext(headers?: Record<string, string>): Record<string, string>;
|
|
85
|
+
/**
|
|
86
|
+
* Initialize the OpenTelemetry SDK with the given configuration.
|
|
87
|
+
* This is called by `TracingModule.forRoot()`. It's idempotent.
|
|
88
|
+
*/
|
|
89
|
+
startSdk(config: import("./types.js").TracingConfig): Promise<void>;
|
|
90
|
+
/** Stop the SDK. Called on process exit / app shutdown. */
|
|
91
|
+
stopSdk(): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
/** Re-export for downstream type users. */
|
|
94
|
+
export type { SpanContext, SpanStatus };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `nexusjs/tracing`.
|
|
3
|
+
*
|
|
4
|
+
* `nexusjs/tracing` is a thin, ergonomic wrapper around the
|
|
5
|
+
* OpenTelemetry API (and, when configured, the SDK). It is
|
|
6
|
+
* intentionally minimal — no global side effects on import.
|
|
7
|
+
*
|
|
8
|
+
* If you don't import `TracingModule.forRoot()`, this module
|
|
9
|
+
* is a complete no-op: no OpenTelemetry packages are loaded
|
|
10
|
+
* and `TracingService` falls back to in-memory no-op spans.
|
|
11
|
+
*/
|
|
12
|
+
import type { SpanContext as OtelSpanContext } from "@opentelemetry/api";
|
|
13
|
+
export type TracingExporter = "otlp-http" | "otlp-grpc" | "console" | "memory";
|
|
14
|
+
export interface TracingConfig {
|
|
15
|
+
/** Service name reported in spans. Defaults to `process.env.OTEL_SERVICE_NAME ?? "nexusjs"`. */
|
|
16
|
+
serviceName?: string;
|
|
17
|
+
/** Service version (sent as `service.version` resource attribute). */
|
|
18
|
+
serviceVersion?: string;
|
|
19
|
+
/** Deployment environment (e.g. "production", "staging"). */
|
|
20
|
+
environment?: string;
|
|
21
|
+
/** Exporter to use. Default: `"otlp-http"`. */
|
|
22
|
+
exporter?: TracingExporter;
|
|
23
|
+
/** OTLP endpoint. Default: `process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"`. */
|
|
24
|
+
endpoint?: string;
|
|
25
|
+
/** Sampling ratio in [0, 1]. 0.0 = drop everything, 1.0 = keep everything. Default: `1.0`. */
|
|
26
|
+
sampleRatio?: number;
|
|
27
|
+
/** Whether to install the Hono HTTP server middleware. Default: `true`. */
|
|
28
|
+
enableHttpInstrumentation?: boolean;
|
|
29
|
+
/** Whether to capture DB spans via `nexusjs/drizzle` (if installed). Default: `true`. */
|
|
30
|
+
enableDbInstrumentation?: boolean;
|
|
31
|
+
/** Extra static resource attributes (service.namespace, deployment.id, ...). */
|
|
32
|
+
resourceAttributes?: Record<string, string>;
|
|
33
|
+
/** When true, exit process on uncaught span error. Default: `false`. */
|
|
34
|
+
throwOnError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Public-facing span context (mirrors OTel's, but vendor-neutral). */
|
|
37
|
+
export interface SpanContext extends OtelSpanContext {
|
|
38
|
+
}
|
|
39
|
+
/** Status of a span — mirrors OTel's `SpanStatusCode`. */
|
|
40
|
+
export type SpanStatus = "unset" | "ok" | "error";
|
|
41
|
+
/** A finished span, returned from `withSpan()` / `endSpan()`. */
|
|
42
|
+
export interface FinishedSpan {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly traceId: string;
|
|
45
|
+
readonly spanId: string;
|
|
46
|
+
readonly parentSpanId?: string;
|
|
47
|
+
readonly startTime: number;
|
|
48
|
+
readonly endTime: number;
|
|
49
|
+
readonly durationMs: number;
|
|
50
|
+
readonly status: SpanStatus;
|
|
51
|
+
readonly attributes: Record<string, string | number | boolean>;
|
|
52
|
+
readonly events: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
time: number;
|
|
55
|
+
attributes?: Record<string, unknown>;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
/** Per-span options. */
|
|
59
|
+
export interface SpanOptions {
|
|
60
|
+
/** Span kind. Default: `"internal"`. */
|
|
61
|
+
kind?: "server" | "client" | "producer" | "consumer" | "internal";
|
|
62
|
+
/** Initial attributes. */
|
|
63
|
+
attributes?: Record<string, string | number | boolean>;
|
|
64
|
+
/** Span start time override (epoch ms). */
|
|
65
|
+
startTime?: number;
|
|
66
|
+
}
|
|
67
|
+
/** Active span handle returned from `startSpan()`. */
|
|
68
|
+
export interface ActiveSpan {
|
|
69
|
+
/** Span name. */
|
|
70
|
+
readonly name: string;
|
|
71
|
+
/** Current trace id (32 hex chars). */
|
|
72
|
+
readonly traceId: string;
|
|
73
|
+
/** Current span id (16 hex chars). */
|
|
74
|
+
readonly spanId: string;
|
|
75
|
+
/** True if the underlying OTel span is a no-op (no SDK configured). */
|
|
76
|
+
readonly isRecording: boolean;
|
|
77
|
+
/** Set an attribute. */
|
|
78
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
79
|
+
/** Set multiple attributes at once. */
|
|
80
|
+
setAttributes(attributes: Record<string, string | number | boolean>): void;
|
|
81
|
+
/** Record an event with optional attributes. */
|
|
82
|
+
addEvent(name: string, attributes?: Record<string, unknown>): void;
|
|
83
|
+
/** Record an exception. */
|
|
84
|
+
recordException(err: unknown): void;
|
|
85
|
+
/** Set the span status to "ok" with an optional description. */
|
|
86
|
+
setStatus(status: "ok" | "error" | "unset", description?: string): void;
|
|
87
|
+
/** End the span. After calling, no other methods are valid. */
|
|
88
|
+
end(): void;
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexusts/tracing",
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"description": "OpenTelemetry distributed tracing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist", "README.md"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "bun run ../../build.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["nexusts", "framework", "bun"],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"peerDependencies": { "@opentelemetry/api": "^1.9.0" },
|
|
22
|
+
"peerDependenciesMeta": { "@opentelemetry/api": { "optional": true } },
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@nexusts/core": "file:../core"
|
|
25
|
+
}
|
|
26
|
+
}
|