@omob/otel-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/dist/enums/exporter-type.enum.d.ts +7 -0
  4. package/dist/enums/exporter-type.enum.js +12 -0
  5. package/dist/enums/exporter-type.enum.js.map +1 -0
  6. package/dist/enums/instrumentation-name.enum.d.ts +43 -0
  7. package/dist/enums/instrumentation-name.enum.js +48 -0
  8. package/dist/enums/instrumentation-name.enum.js.map +1 -0
  9. package/dist/enums/otlp-protocol.enum.d.ts +5 -0
  10. package/dist/enums/otlp-protocol.enum.js +10 -0
  11. package/dist/enums/otlp-protocol.enum.js.map +1 -0
  12. package/dist/enums/propagator-type.enum.d.ts +7 -0
  13. package/dist/enums/propagator-type.enum.js +12 -0
  14. package/dist/enums/propagator-type.enum.js.map +1 -0
  15. package/dist/enums/telemetry-error-code.enum.d.ts +8 -0
  16. package/dist/enums/telemetry-error-code.enum.js +13 -0
  17. package/dist/enums/telemetry-error-code.enum.js.map +1 -0
  18. package/dist/enums/telemetry-signal.enum.d.ts +5 -0
  19. package/dist/enums/telemetry-signal.enum.js +10 -0
  20. package/dist/enums/telemetry-signal.enum.js.map +1 -0
  21. package/dist/errors/telemetry-config.error.d.ts +6 -0
  22. package/dist/errors/telemetry-config.error.js +12 -0
  23. package/dist/errors/telemetry-config.error.js.map +1 -0
  24. package/dist/factories/instrumentation.factory.d.ts +7 -0
  25. package/dist/factories/instrumentation.factory.js +30 -0
  26. package/dist/factories/instrumentation.factory.js.map +1 -0
  27. package/dist/factories/log-processor.factory.d.ts +6 -0
  28. package/dist/factories/log-processor.factory.js +31 -0
  29. package/dist/factories/log-processor.factory.js.map +1 -0
  30. package/dist/factories/metric-reader.factory.d.ts +7 -0
  31. package/dist/factories/metric-reader.factory.js +51 -0
  32. package/dist/factories/metric-reader.factory.js.map +1 -0
  33. package/dist/factories/otlp-exporter.factory.d.ts +6 -0
  34. package/dist/factories/otlp-exporter.factory.js +46 -0
  35. package/dist/factories/otlp-exporter.factory.js.map +1 -0
  36. package/dist/factories/propagator.factory.d.ts +7 -0
  37. package/dist/factories/propagator.factory.js +36 -0
  38. package/dist/factories/propagator.factory.js.map +1 -0
  39. package/dist/factories/resource.factory.d.ts +6 -0
  40. package/dist/factories/resource.factory.js +23 -0
  41. package/dist/factories/resource.factory.js.map +1 -0
  42. package/dist/factories/sampler.factory.d.ts +5 -0
  43. package/dist/factories/sampler.factory.js +21 -0
  44. package/dist/factories/sampler.factory.js.map +1 -0
  45. package/dist/factories/sdk.factory.d.ts +6 -0
  46. package/dist/factories/sdk.factory.js +46 -0
  47. package/dist/factories/sdk.factory.js.map +1 -0
  48. package/dist/factories/trace-exporter.factory.d.ts +7 -0
  49. package/dist/factories/trace-exporter.factory.js +36 -0
  50. package/dist/factories/trace-exporter.factory.js.map +1 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.js +42 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/processors/attribute-sanitizer.processor.d.ts +8 -0
  55. package/dist/processors/attribute-sanitizer.processor.js +23 -0
  56. package/dist/processors/attribute-sanitizer.processor.js.map +1 -0
  57. package/dist/services/span.service.d.ts +6 -0
  58. package/dist/services/span.service.js +39 -0
  59. package/dist/services/span.service.js.map +1 -0
  60. package/dist/services/telemetry.service.d.ts +19 -0
  61. package/dist/services/telemetry.service.js +104 -0
  62. package/dist/services/telemetry.service.js.map +1 -0
  63. package/dist/telemetry.types.d.ts +100 -0
  64. package/dist/telemetry.types.js +3 -0
  65. package/dist/telemetry.types.js.map +1 -0
  66. package/dist/utils/gcp-credentials.d.ts +5 -0
  67. package/dist/utils/gcp-credentials.js +14 -0
  68. package/dist/utils/gcp-credentials.js.map +1 -0
  69. package/dist/utils/optional-dependency.d.ts +1 -0
  70. package/dist/utils/optional-dependency.js +19 -0
  71. package/dist/utils/optional-dependency.js.map +1 -0
  72. package/dist/utils/otlp-options.d.ts +6 -0
  73. package/dist/utils/otlp-options.js +11 -0
  74. package/dist/utils/otlp-options.js.map +1 -0
  75. package/package.json +97 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayodeji Abodunrin
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,398 @@
1
+ # @omob/otel-kit
2
+
3
+ OpenTelemetry setup for Node services, in one function call.
4
+
5
+ ```mermaid
6
+ flowchart LR
7
+ R(["a request arrives"])
8
+
9
+ R --> M["METRICS<br/>counts and latencies, aggregated<br/>-<br/>is something wrong?"]
10
+ R --> T["TRACES<br/>one request, span by span<br/>-<br/>where is it wrong?"]
11
+ R --> L["LOGS<br/>the lines you wrote<br/>-<br/>why is it wrong?"]
12
+
13
+ M -- "a latency spike,<br/>at 14:02" --> T
14
+ T -- "trace_id stamped<br/>on every line" --> L
15
+
16
+ style T stroke-width:3px
17
+ ```
18
+
19
+ Those are the three signals of observability, and they answer different questions. Metrics tell you *something* broke. Traces tell you *where* — which service, which query, which call. Logs tell you *why*, once you know where to look.
20
+
21
+ Tracing is the one that connects the other two, and it is what this package is mostly for. You pick where traces, metrics and logs go; it handles the SDK, the sampling, the shutdown flush, and the boilerplate around spans — and stamps `trace_id` into your logs so the third column lines up with the second.
22
+
23
+ ## The words, briefly
24
+
25
+ If you're new to OpenTelemetry, this is everything you need to read the rest of this page.
26
+
27
+ A **trace** is the story of one request, start to finish. A **span** is one timed step inside that story. Spans nest, so a trace is a tree:
28
+
29
+ ```
30
+ GET /login 240ms ← the trace starts here
31
+ ├─ user.lookup 180ms
32
+ │ └─ mongodb.find 175ms ← this one you get for free
33
+ └─ token.generate 12ms
34
+ ```
35
+
36
+ Each span carries a name, a start and end time, a status (did it fail), and **attributes** — key/value labels like `user.id` or `http.method`. Every span in that tree shares one **trace ID**, which is how the tree gets reassembled at the other end.
37
+
38
+ You get most spans for free. **Instrumentation** is code that wraps common libraries — HTTP, Mongo, Postgres, Redis — and opens a span whenever they're used. You never call it. `withSpan` is for the steps only you know are worth timing, like the two named above.
39
+
40
+ **Sampling** is deciding what to keep. Tracing every request at scale is expensive, so `sampleRatio: 0.1` keeps a tenth. The choice is made once at the root and the whole tree follows it, so you never get half a trace.
41
+
42
+ **Propagation** is how a trace survives a network hop. Your service puts the trace ID in an outgoing header; the next service reads it and continues the same trace instead of starting a new one. Both sides must agree on the header format — that's what `propagators` configures.
43
+
44
+ An **exporter** is where the finished data is sent: your collector, Jaeger, Google Cloud, or the console. A **resource** is the facts about the service itself — name, version, environment — stamped on everything you send.
45
+
46
+ Traces answer "what happened in this one request". **Metrics** are numbers over time ("requests per second"), and **logs** are the text lines you already write. This package can send all three; most people start with traces alone.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ npm install @omob/otel-kit @opentelemetry/api
52
+ ```
53
+
54
+ OTLP and Prometheus exporters are included. Google Cloud is the only one you install separately:
55
+
56
+ ```bash
57
+ npm install @google-cloud/opentelemetry-cloud-trace-exporter \
58
+ @google-cloud/opentelemetry-cloud-monitoring-exporter
59
+ ```
60
+
61
+ If you pick an exporter you haven't installed, startup fails and tells you which package to install.
62
+
63
+ ## Quick start
64
+
65
+ Create `src/instrumentation.ts`:
66
+
67
+ ```ts
68
+ import "dotenv/config";
69
+ import { ExporterType, Telemetry } from "@omob/otel-kit";
70
+
71
+ Telemetry.start({
72
+ serviceName: "my-service",
73
+ serviceVersion: process.env.APP_VERSION,
74
+ environment: process.env.NODE_ENV,
75
+ enabled: process.env.NODE_ENV !== "test",
76
+ traces: {
77
+ exporter: ExporterType.OTLP,
78
+ otlp: { url: "http://localhost:4318/v1/traces" },
79
+ sampleRatio: Number(process.env.OTEL_TRACES_SAMPLE_RATIO ?? 1),
80
+ },
81
+ instrumentation: { ignoreIncomingPaths: ["/health"] },
82
+ });
83
+ ```
84
+
85
+ Load it before your app:
86
+
87
+ ```json
88
+ { "scripts": { "start": "node --require ./dist/instrumentation.js dist/server.js" } }
89
+ ```
90
+
91
+ That's it. HTTP, database and framework calls are traced automatically.
92
+
93
+ Keep the ratio at 1 while you are setting things up. Sampling below 1 is a production concern, and turning it down before you have seen a single trace is the most common reason nothing appears in a backend. Dial it down later with the env var.
94
+
95
+ **On Fastify, turn its instrumentation on.** It ships disabled, along with `fs`, and there is a wrinkle worth reading below:
96
+
97
+ ```ts
98
+ instrumentation: { enable: [InstrumentationName.FASTIFY], ignoreIncomingPaths: ["/health"] }
99
+ ```
100
+
101
+ Without it, every request is one bare `GET` span with no `http.route`, so nothing groups by route. Express, Koa, Hapi, NestJS, Mongo, Postgres, Redis, Kafka and outbound HTTP need no such step — they are on by default. The wrinkle: the bundled Fastify instrumentation is deprecated upstream, which is *why* it is disabled. It works, and the alternative is covered under [Recipes](#recipes).
102
+
103
+ ### Why `--require`
104
+
105
+ Instrumentation can only patch libraries loaded *after* it starts. `--require` guarantees that.
106
+
107
+ Importing it at the top of your entry file also works, as long as nothing you want traced is imported above it. One reordered import and tracing silently stops — hence the flag.
108
+
109
+ ## Your own spans
110
+
111
+ `withSpan` runs your function inside a span. It starts the span, makes it the parent of anything that happens inside, ends it when your function settles, and records the error if one is thrown.
112
+
113
+ Add one when you want a step to show up as its own line in the trace: a slow query, an external API call, a step you suspect. Skip it for cheap in-memory work — a span costs more than the code it measures.
114
+
115
+ ```ts
116
+ import { withSpan } from "@omob/otel-kit";
117
+
118
+ async function login({ email, password }) {
119
+ return withSpan("login", { attributes: { "auth.method": "password" } }, async (span) => {
120
+ const user = await findUser(email);
121
+ span.setAttribute("user.id", user.id);
122
+
123
+ return withSpan("token.generate", () => generateToken(user));
124
+ });
125
+ }
126
+ ```
127
+
128
+ Throw anywhere inside and the span is marked failed, the exception is recorded, and the error still propagates to your caller unchanged. Spans always end, on success or failure.
129
+
130
+ Not every failure is a fault. A wrong password is an expected outcome, and marking it as a span error means your error rate tracks how often users mistype. Pass `isError` to say which throws actually count:
131
+
132
+ ```ts
133
+ withSpan("login", { isError: (e) => !(e instanceof AppError) || e.statusCode >= 500 }, handler);
134
+ ```
135
+
136
+ Never put emails, tokens or passwords in attributes — spans are stored unredacted.
137
+
138
+ Two more helpers:
139
+
140
+ ```ts
141
+ import { currentTraceId, getTracer } from "@omob/otel-kit";
142
+
143
+ currentTraceId(); // trace id of the active span, or undefined
144
+ getTracer("auth-module"); // pass as `tracer` in withSpan options to name the scope
145
+ ```
146
+
147
+ `currentTraceId()` is worth putting in your error handler, so support can jump from an error response straight to the trace:
148
+
149
+ ```ts
150
+ fastify.setErrorHandler((err, request, reply) =>
151
+ reply.status(err.statusCode ?? 500).send({ message: err.message, traceId: currentTraceId() })
152
+ );
153
+ ```
154
+
155
+ ## Shutting down
156
+
157
+ Spans sit in a batch buffer for up to five seconds, so a process that exits without flushing loses them — on every deploy, which is exactly when you want them.
158
+
159
+ By default the package listens for SIGTERM and SIGINT, flushes, and then **hands the signal back**: your own handlers still run, and if there are none the process terminates with the conventional exit code (143 for SIGTERM, 130 for SIGINT). It never calls `process.exit` itself unless you ask it to with `exitOnSignal: true`.
160
+
161
+ If your app already drains connections, own the order yourself — close the server first so no new spans are created, then flush:
162
+
163
+ ```ts
164
+ Telemetry.start({ ..., handleShutdownSignals: false });
165
+
166
+ const drain = async (code: number) => {
167
+ await app.close();
168
+ await Telemetry.shutdown();
169
+ process.exit(code);
170
+ };
171
+
172
+ process.on("SIGTERM", () => drain(143));
173
+ process.on("SIGINT", () => drain(130));
174
+ ```
175
+
176
+ Do not leave `handleShutdownSignals` on *and* call `Telemetry.shutdown()` from your own handler — one flush runs, both callers await it, but the ordering of your drain is no longer guaranteed.
177
+
178
+ ## Turning things off
179
+
180
+ Every signal is optional and off by default. Omit what you don't want:
181
+
182
+ ```ts
183
+ Telemetry.start({
184
+ serviceName: "my-service",
185
+ traces: { exporter: ExporterType.OTLP, otlp: { url } },
186
+ // no metrics block, no logs block — nothing is created for them
187
+ });
188
+ ```
189
+
190
+ To disable everything at once, set `enabled: false`. `Telemetry.start` becomes a no-op, no SDK is loaded. Use it for tests.
191
+
192
+ ## When configuration is rejected
193
+
194
+ A telemetry mistake should not stop your service from serving traffic. If the configuration is rejected, `Telemetry.start` does **not** throw: it logs, leaves telemetry off, and lets your app boot. Pass `onStartupError` to route that into your own logger or alerting:
195
+
196
+ ```ts
197
+ Telemetry.start({ ..., onStartupError: (error) => logger.error({ error }, "telemetry disabled") });
198
+ ```
199
+
200
+ Pass a handler that rethrows if you would rather fail the boot.
201
+
202
+ ## Options
203
+
204
+ Only `serviceName` is required. Everything else has a working default.
205
+
206
+ **The service**
207
+
208
+ | Option | Default | What it does |
209
+ | --- | --- | --- |
210
+ | `serviceName` | **required** | Name your service appears under. |
211
+ | `serviceVersion` | — | Shows on every span as `service.version`. |
212
+ | `environment` | — | Shows as `deployment.environment.name`. |
213
+ | `enabled` | `true` | `false` turns everything off and loads no SDK. |
214
+ | `resourceAttributes` | `{}` | Extra attributes on every span, metric and log. |
215
+ | `resourceDetection` | `true` | Auto-detects host and process attributes. Stamps `process.command_args` — your argv — on everything, so set `false` if you pass secrets as flags. |
216
+
217
+ **Traces**
218
+
219
+ | Option | Default | What it does |
220
+ | --- | --- | --- |
221
+ | `traces.exporter` | `none` | `none` · `console` · `otlp` · `gcp` |
222
+ | `traces.sampleRatio` | keep all | `0.1` keeps 10%. Children follow their parent's decision. |
223
+ | `traces.sampler` | — | A sampler of your own. Takes precedence over `sampleRatio`. |
224
+ | `traces.otlp.url` | — | Collector endpoint. Also takes `headers` and `timeoutMillis`. |
225
+ | `traces.otlp.protocol` | `http/protobuf` | `http/protobuf` · `http/json` · `grpc` |
226
+ | `traces.gcp.projectId` | `$GCP_PROJECT_ID` | Also takes `keyFile`; falls back to application default credentials. |
227
+ | `traces.batch` | SDK defaults | `maxQueueSize`, `maxExportBatchSize`, `scheduledDelayMillis`, `exportTimeoutMillis`. Raise the queue if you drop spans under load. |
228
+ | `traces.additionalProcessors` | `[]` | Extra span processors — scrub attributes, enrich spans, or dual-write to a second collector. |
229
+ | `traces.sanitizeAttributes` | `true` | Drops `NaN` and `Infinity` attribute values, which some backends cannot represent. |
230
+ | `spanLimits.attributeValueLengthLimit` | `4096` | Caps attribute size so one oversized request can't produce an unbounded span. |
231
+
232
+ **Metrics and logs**
233
+
234
+ | Option | Default | What it does |
235
+ | --- | --- | --- |
236
+ | `metrics.exporter` | `none` | `none` · `console` · `otlp` · `gcp` · `prometheus` |
237
+ | `metrics.exportIntervalMillis` | `60000` | How often metrics are pushed. Prometheus ignores it — it's pull-based. |
238
+ | `metrics.prometheus` | `127.0.0.1:9464` | `host`, `port`, `endpoint`. Binds loopback by default — the endpoint is unauthenticated, so only widen it behind a private network. |
239
+ | `metrics.views` | `[]` | Histogram buckets and cardinality limits. |
240
+ | `logs.exporter` | `none` | `none` · `console` · `otlp` |
241
+
242
+ **Instrumentation and propagation**
243
+
244
+ | Option | Default | What it does |
245
+ | --- | --- | --- |
246
+ | `instrumentation.disable` | `[]` | Instrumentations to switch off, e.g. `[InstrumentationName.DNS]`. |
247
+ | `instrumentation.enable` | `[]` | Switch on one that's off by default. Beats `disable`. |
248
+ | `instrumentation.ignoreIncomingPaths` | `[]` | No spans for these paths. Put your health check here. |
249
+ | `instrumentation.config` | `{}` | Options for individual instrumentations, passed to OpenTelemetry unchanged. |
250
+ | `instrumentation.additional` | `[]` | Instrumentations outside the auto set — community ones, or your own. |
251
+ | `propagators` | `tracecontext`, `baggage` | Trace context formats to read and write. |
252
+
253
+ **Lifecycle and diagnostics**
254
+
255
+ | Option | Default | What it does |
256
+ | --- | --- | --- |
257
+ | `handleShutdownSignals` | `true` | Flush on SIGTERM/SIGINT, then hand the signal back. |
258
+ | `exitOnSignal` | `false` | Call `process.exit(0)` after flushing instead of handing the signal back. |
259
+ | `shutdownTimeoutMillis` | `5000` | Give up if the flush hangs, so shutdown can't stall. |
260
+ | `onStartupError` | logs and continues | Called instead of throwing when the configuration is rejected. |
261
+ | `diagLogLevel` | off | Turns on OpenTelemetry's own internal logging. |
262
+ | `diagLogger` | console | Where that internal logging goes. |
263
+
264
+ ## Recipes
265
+
266
+ **Jaeger** — Jaeger accepts OTLP directly, so there's no Jaeger exporter to install:
267
+
268
+ ```ts
269
+ traces: { exporter: ExporterType.OTLP, otlp: { url: "http://jaeger:4318/v1/traces" } }
270
+ ```
271
+
272
+ If services calling you send Jaeger's `uber-trace-id` header instead of W3C `traceparent`, accept both — otherwise their trace and yours end up unlinked:
273
+
274
+ ```ts
275
+ propagators: [PropagatorType.TRACE_CONTEXT, PropagatorType.BAGGAGE, PropagatorType.JAEGER]
276
+ ```
277
+
278
+ All listed formats are written on outgoing calls, and an incoming call joins the upstream trace if any of them matches. `B3` and `B3_MULTI` cover Zipkin and Envoy/Istio meshes.
279
+
280
+ **Per-instrumentation options** — anything documented upstream works here. `instrumentation.config` is keyed by package name and handed to OpenTelemetry unchanged:
281
+
282
+ ```ts
283
+ instrumentation: {
284
+ enable: [InstrumentationName.FASTIFY],
285
+ ignoreIncomingPaths: ["/health"],
286
+ config: {
287
+ [InstrumentationName.HTTP]: {
288
+ ignoreOutgoingRequestHook: (options) => options.hostname === "metrics.internal",
289
+ headersToSpanAttributes: { server: { requestHeaders: ["x-request-id"] } },
290
+ },
291
+ [InstrumentationName.PG]: { enhancedDatabaseReporting: true },
292
+ [InstrumentationName.FASTIFY]: { requestHook: (span, info) => span.setAttribute("plugin", info.pluginName) },
293
+ },
294
+ }
295
+ ```
296
+
297
+ `disable`, `enable` and `ignoreIncomingPaths` are conveniences layered on top of the same map, and they win where they overlap — so `ignoreIncomingPaths` merges into your HTTP options rather than replacing them.
298
+
299
+ **Every instrumentation in [`@opentelemetry/auto-instrumentations-node`](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/auto-instrumentations-node) works here** — all 41 of them, Express, Koa, Hapi, NestJS, Restify, Postgres, MySQL, Mongo, Redis, Kafka, gRPC, AWS SDK and the rest. They are on by default, `InstrumentationName` has an entry for each, and their own options go through `instrumentation.config` untouched.
300
+
301
+ Each documents its options in its own README:
302
+
303
+ - most live in [opentelemetry-js-contrib](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages) under `packages/instrumentation-<name>`
304
+ - HTTP and gRPC live in [opentelemetry-js](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages) under `experimental/packages/opentelemetry-instrumentation-<name>`
305
+
306
+ `InstrumentationName` values *are* the package names, so the enum entry tells you which README to open.
307
+
308
+ Anything **not** in that set — a community instrumentation, or one you wrote — goes through `instrumentation.additional`, which takes instrumentation instances directly.
309
+
310
+ Three defaults worth knowing, all decided upstream rather than here:
311
+
312
+ - `fs` is off by default. It emits a span per file read and drowns everything else.
313
+ - `fastify` is off by default because `@opentelemetry/instrumentation-fastify` is **deprecated** in favour of [`@fastify/otel`](https://www.npmjs.com/package/@fastify/otel), maintained by the Fastify team. `enable: [InstrumentationName.FASTIFY]` still works and still produces route spans, but it is unmaintained; `@fastify/otel` registers as a Fastify plugin and reports through the same global API this package sets up.
314
+ - database instrumentations replace query values with `?`. `enhancedDatabaseReporting: true` puts the real parameters in your spans — think about customer data before turning it on.
315
+
316
+ **A gRPC collector** — OTLP defaults to HTTP/protobuf on port 4318. For a collector speaking gRPC on 4317:
317
+
318
+ ```ts
319
+ traces: { exporter: ExporterType.OTLP, otlp: { protocol: OtlpProtocol.GRPC, url: "http://collector:4317" } }
320
+ ```
321
+
322
+ Note the HTTP protocols need the full signal path (`/v1/traces`); gRPC takes the base URL. A missing path is a silent 404 on every export.
323
+
324
+ **Scrubbing attributes before they leave** — add your own span processor:
325
+
326
+ ```ts
327
+ traces: { exporter: ExporterType.OTLP, additionalProcessors: [new RedactingSpanProcessor()] }
328
+ ```
329
+
330
+ **Prometheus** — serves a scrape endpoint instead of pushing:
331
+
332
+ ```ts
333
+ metrics: { exporter: ExporterType.PROMETHEUS, prometheus: { port: 9464 } }
334
+ ```
335
+
336
+ Then scrape `http://your-service:9464/metrics`.
337
+
338
+ **Google Cloud**:
339
+
340
+ ```ts
341
+ traces: { exporter: ExporterType.GCP, gcp: { projectId: "my-project" } }
342
+ ```
343
+
344
+ Uses `GOOGLE_APPLICATION_CREDENTIALS` if it points at a readable file, otherwise application default credentials.
345
+
346
+ **Seeing spans locally** — no collector needed:
347
+
348
+ ```ts
349
+ traces: { exporter: ExporterType.CONSOLE, sampleRatio: 1 }
350
+ ```
351
+
352
+ Spans print on shutdown, since they're batched.
353
+
354
+ **Quieter traces** — the noisiest instrumentations are usually DNS and net, especially with a database driver reconnecting:
355
+
356
+ ```ts
357
+ instrumentation: { disable: [InstrumentationName.DNS, InstrumentationName.NET] }
358
+ ```
359
+
360
+ ## Troubleshooting
361
+
362
+ A rejected configuration produces a `TelemetryConfigError`. It is **reported, not thrown** — telemetry switches itself off and your service still boots. `onStartupError` decides where that goes; by default it is logged.
363
+
364
+ | Error code | Cause |
365
+ | --- | --- |
366
+ | `MISSING_SERVICE_NAME` | `serviceName` empty or missing. |
367
+ | `INVALID_SAMPLE_RATIO` | `sampleRatio` outside 0–1. |
368
+ | `UNSUPPORTED_EXPORTER` | Exporter can't handle that signal, e.g. `prometheus` for traces. |
369
+ | `UNSUPPORTED_PROPAGATOR` | Unknown propagator name. |
370
+ | `MISSING_OPTIONAL_DEPENDENCY` | Exporter selected but its package isn't installed. |
371
+
372
+ **No traces showing up? Read the `trace_flags` on your own log lines first.** If you use pino, bunyan or winston, the log instrumentation stamps every line with the active trace:
373
+
374
+ ```json
375
+ {"trace_id":"9d497527a7ec14c5a81325251113283d","span_id":"b58894198a10765d","trace_flags":"00"}
376
+ ```
377
+
378
+ That one field splits the problem in half:
379
+
380
+ - **`trace_flags: "00"`** — the span was created and then dropped by the sampler. Nothing was ever sent, so the collector is irrelevant. Raise `sampleRatio` to 1, or check whether an inbound `traceparent` arrived already marked unsampled — parent-based sampling honours the caller's decision.
381
+ - **`trace_flags: "01"`** — the span was sampled and handed to the exporter, so the problem is downstream: the endpoint, the path, or the network.
382
+ - **no `trace_id` at all** — the SDK never started, or it started after your app loaded. Check the `--require` flag.
383
+
384
+ Then turn on OpenTelemetry's own logging — it is off by default, which is why a bad endpoint or an unreachable collector produces silence rather than an error:
385
+
386
+ ```ts
387
+ import { DiagLogLevel } from "@opentelemetry/api";
388
+
389
+ Telemetry.start({ ..., diagLogLevel: DiagLogLevel.ERROR });
390
+ ```
391
+
392
+ Pass `diagLogger` to send it to your own logger instead of the console. With that on, most causes announce themselves. Without it, check in order: is `enabled` true; is `traces.exporter` something other than `none`; is the path in `ignoreIncomingPaths`; is `sampleRatio` dropping them; is the collector URL reachable from inside the container.
393
+
394
+ **Jaeger returns a 500 with `json: unsupported value: NaN`?** A span carried a `NaN` numeric attribute, and Go's JSON encoder cannot represent one — so Jaeger fails the whole trace, not just that attribute. Some instrumentations produce it by parsing a port or a header that was not there. The package drops non-finite attribute values before export, so this should not reach you; if you have set `sanitizeAttributes: false`, that is why.
395
+
396
+ **Everything arrives under the wrong service name?** `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` are read by the SDK's own resource detectors, and detected attributes win over the ones you pass in code. A service mesh or a shared Helm chart injecting either will silently rename your service. Set `resourceDetection: false` to stop that.
397
+
398
+ **Traces stop at your service** — a caller's trace doesn't continue into yours: they're probably using a propagation format you haven't listed in `propagators`.
@@ -0,0 +1,7 @@
1
+ export declare enum ExporterType {
2
+ NONE = "none",
3
+ CONSOLE = "console",
4
+ OTLP = "otlp",
5
+ GCP = "gcp",
6
+ PROMETHEUS = "prometheus"
7
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExporterType = void 0;
4
+ var ExporterType;
5
+ (function (ExporterType) {
6
+ ExporterType["NONE"] = "none";
7
+ ExporterType["CONSOLE"] = "console";
8
+ ExporterType["OTLP"] = "otlp";
9
+ ExporterType["GCP"] = "gcp";
10
+ ExporterType["PROMETHEUS"] = "prometheus";
11
+ })(ExporterType || (exports.ExporterType = ExporterType = {}));
12
+ //# sourceMappingURL=exporter-type.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exporter-type.enum.js","sourceRoot":"","sources":["../../src/enums/exporter-type.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAMX;AAND,WAAY,YAAY;IACtB,6BAAa,CAAA;IACb,mCAAmB,CAAA;IACnB,6BAAa,CAAA;IACb,2BAAW,CAAA;IACX,yCAAyB,CAAA;AAC3B,CAAC,EANW,YAAY,4BAAZ,YAAY,QAMvB"}
@@ -0,0 +1,43 @@
1
+ export declare enum InstrumentationName {
2
+ AMQPLIB = "@opentelemetry/instrumentation-amqplib",
3
+ AWS_LAMBDA = "@opentelemetry/instrumentation-aws-lambda",
4
+ AWS_SDK = "@opentelemetry/instrumentation-aws-sdk",
5
+ BUNYAN = "@opentelemetry/instrumentation-bunyan",
6
+ CASSANDRA_DRIVER = "@opentelemetry/instrumentation-cassandra-driver",
7
+ CONNECT = "@opentelemetry/instrumentation-connect",
8
+ CUCUMBER = "@opentelemetry/instrumentation-cucumber",
9
+ DATALOADER = "@opentelemetry/instrumentation-dataloader",
10
+ DNS = "@opentelemetry/instrumentation-dns",
11
+ EXPRESS = "@opentelemetry/instrumentation-express",
12
+ FASTIFY = "@opentelemetry/instrumentation-fastify",
13
+ FS = "@opentelemetry/instrumentation-fs",
14
+ GENERIC_POOL = "@opentelemetry/instrumentation-generic-pool",
15
+ GRAPHQL = "@opentelemetry/instrumentation-graphql",
16
+ GRPC = "@opentelemetry/instrumentation-grpc",
17
+ HAPI = "@opentelemetry/instrumentation-hapi",
18
+ HTTP = "@opentelemetry/instrumentation-http",
19
+ IOREDIS = "@opentelemetry/instrumentation-ioredis",
20
+ KAFKAJS = "@opentelemetry/instrumentation-kafkajs",
21
+ KNEX = "@opentelemetry/instrumentation-knex",
22
+ KOA = "@opentelemetry/instrumentation-koa",
23
+ LRU_MEMOIZER = "@opentelemetry/instrumentation-lru-memoizer",
24
+ MEMCACHED = "@opentelemetry/instrumentation-memcached",
25
+ MONGODB = "@opentelemetry/instrumentation-mongodb",
26
+ MONGOOSE = "@opentelemetry/instrumentation-mongoose",
27
+ MYSQL = "@opentelemetry/instrumentation-mysql",
28
+ MYSQL2 = "@opentelemetry/instrumentation-mysql2",
29
+ NESTJS_CORE = "@opentelemetry/instrumentation-nestjs-core",
30
+ NET = "@opentelemetry/instrumentation-net",
31
+ OPENAI = "@opentelemetry/instrumentation-openai",
32
+ ORACLEDB = "@opentelemetry/instrumentation-oracledb",
33
+ PG = "@opentelemetry/instrumentation-pg",
34
+ PINO = "@opentelemetry/instrumentation-pino",
35
+ REDIS = "@opentelemetry/instrumentation-redis",
36
+ RESTIFY = "@opentelemetry/instrumentation-restify",
37
+ ROUTER = "@opentelemetry/instrumentation-router",
38
+ RUNTIME_NODE = "@opentelemetry/instrumentation-runtime-node",
39
+ SOCKET_IO = "@opentelemetry/instrumentation-socket.io",
40
+ TEDIOUS = "@opentelemetry/instrumentation-tedious",
41
+ UNDICI = "@opentelemetry/instrumentation-undici",
42
+ WINSTON = "@opentelemetry/instrumentation-winston"
43
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InstrumentationName = void 0;
4
+ var InstrumentationName;
5
+ (function (InstrumentationName) {
6
+ InstrumentationName["AMQPLIB"] = "@opentelemetry/instrumentation-amqplib";
7
+ InstrumentationName["AWS_LAMBDA"] = "@opentelemetry/instrumentation-aws-lambda";
8
+ InstrumentationName["AWS_SDK"] = "@opentelemetry/instrumentation-aws-sdk";
9
+ InstrumentationName["BUNYAN"] = "@opentelemetry/instrumentation-bunyan";
10
+ InstrumentationName["CASSANDRA_DRIVER"] = "@opentelemetry/instrumentation-cassandra-driver";
11
+ InstrumentationName["CONNECT"] = "@opentelemetry/instrumentation-connect";
12
+ InstrumentationName["CUCUMBER"] = "@opentelemetry/instrumentation-cucumber";
13
+ InstrumentationName["DATALOADER"] = "@opentelemetry/instrumentation-dataloader";
14
+ InstrumentationName["DNS"] = "@opentelemetry/instrumentation-dns";
15
+ InstrumentationName["EXPRESS"] = "@opentelemetry/instrumentation-express";
16
+ InstrumentationName["FASTIFY"] = "@opentelemetry/instrumentation-fastify";
17
+ InstrumentationName["FS"] = "@opentelemetry/instrumentation-fs";
18
+ InstrumentationName["GENERIC_POOL"] = "@opentelemetry/instrumentation-generic-pool";
19
+ InstrumentationName["GRAPHQL"] = "@opentelemetry/instrumentation-graphql";
20
+ InstrumentationName["GRPC"] = "@opentelemetry/instrumentation-grpc";
21
+ InstrumentationName["HAPI"] = "@opentelemetry/instrumentation-hapi";
22
+ InstrumentationName["HTTP"] = "@opentelemetry/instrumentation-http";
23
+ InstrumentationName["IOREDIS"] = "@opentelemetry/instrumentation-ioredis";
24
+ InstrumentationName["KAFKAJS"] = "@opentelemetry/instrumentation-kafkajs";
25
+ InstrumentationName["KNEX"] = "@opentelemetry/instrumentation-knex";
26
+ InstrumentationName["KOA"] = "@opentelemetry/instrumentation-koa";
27
+ InstrumentationName["LRU_MEMOIZER"] = "@opentelemetry/instrumentation-lru-memoizer";
28
+ InstrumentationName["MEMCACHED"] = "@opentelemetry/instrumentation-memcached";
29
+ InstrumentationName["MONGODB"] = "@opentelemetry/instrumentation-mongodb";
30
+ InstrumentationName["MONGOOSE"] = "@opentelemetry/instrumentation-mongoose";
31
+ InstrumentationName["MYSQL"] = "@opentelemetry/instrumentation-mysql";
32
+ InstrumentationName["MYSQL2"] = "@opentelemetry/instrumentation-mysql2";
33
+ InstrumentationName["NESTJS_CORE"] = "@opentelemetry/instrumentation-nestjs-core";
34
+ InstrumentationName["NET"] = "@opentelemetry/instrumentation-net";
35
+ InstrumentationName["OPENAI"] = "@opentelemetry/instrumentation-openai";
36
+ InstrumentationName["ORACLEDB"] = "@opentelemetry/instrumentation-oracledb";
37
+ InstrumentationName["PG"] = "@opentelemetry/instrumentation-pg";
38
+ InstrumentationName["PINO"] = "@opentelemetry/instrumentation-pino";
39
+ InstrumentationName["REDIS"] = "@opentelemetry/instrumentation-redis";
40
+ InstrumentationName["RESTIFY"] = "@opentelemetry/instrumentation-restify";
41
+ InstrumentationName["ROUTER"] = "@opentelemetry/instrumentation-router";
42
+ InstrumentationName["RUNTIME_NODE"] = "@opentelemetry/instrumentation-runtime-node";
43
+ InstrumentationName["SOCKET_IO"] = "@opentelemetry/instrumentation-socket.io";
44
+ InstrumentationName["TEDIOUS"] = "@opentelemetry/instrumentation-tedious";
45
+ InstrumentationName["UNDICI"] = "@opentelemetry/instrumentation-undici";
46
+ InstrumentationName["WINSTON"] = "@opentelemetry/instrumentation-winston";
47
+ })(InstrumentationName || (exports.InstrumentationName = InstrumentationName = {}));
48
+ //# sourceMappingURL=instrumentation-name.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation-name.enum.js","sourceRoot":"","sources":["../../src/enums/instrumentation-name.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,mBA0CX;AA1CD,WAAY,mBAAmB;IAC7B,yEAAkD,CAAA;IAClD,+EAAwD,CAAA;IACxD,yEAAkD,CAAA;IAClD,uEAAgD,CAAA;IAChD,2FAAoE,CAAA;IACpE,yEAAkD,CAAA;IAClD,2EAAoD,CAAA;IACpD,+EAAwD,CAAA;IACxD,iEAA0C,CAAA;IAC1C,yEAAkD,CAAA;IAClD,yEAAkD,CAAA;IAClD,+DAAwC,CAAA;IACxC,mFAA4D,CAAA;IAC5D,yEAAkD,CAAA;IAClD,mEAA4C,CAAA;IAC5C,mEAA4C,CAAA;IAC5C,mEAA4C,CAAA;IAC5C,yEAAkD,CAAA;IAClD,yEAAkD,CAAA;IAClD,mEAA4C,CAAA;IAC5C,iEAA0C,CAAA;IAC1C,mFAA4D,CAAA;IAC5D,6EAAsD,CAAA;IACtD,yEAAkD,CAAA;IAClD,2EAAoD,CAAA;IACpD,qEAA8C,CAAA;IAC9C,uEAAgD,CAAA;IAChD,iFAA0D,CAAA;IAC1D,iEAA0C,CAAA;IAC1C,uEAAgD,CAAA;IAChD,2EAAoD,CAAA;IACpD,+DAAwC,CAAA;IACxC,mEAA4C,CAAA;IAC5C,qEAA8C,CAAA;IAC9C,yEAAkD,CAAA;IAClD,uEAAgD,CAAA;IAChD,mFAA4D,CAAA;IAC5D,6EAAsD,CAAA;IACtD,yEAAkD,CAAA;IAClD,uEAAgD,CAAA;IAChD,yEAAkD,CAAA;AACpD,CAAC,EA1CW,mBAAmB,mCAAnB,mBAAmB,QA0C9B"}
@@ -0,0 +1,5 @@
1
+ export declare enum OtlpProtocol {
2
+ HTTP_PROTOBUF = "http/protobuf",
3
+ HTTP_JSON = "http/json",
4
+ GRPC = "grpc"
5
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OtlpProtocol = void 0;
4
+ var OtlpProtocol;
5
+ (function (OtlpProtocol) {
6
+ OtlpProtocol["HTTP_PROTOBUF"] = "http/protobuf";
7
+ OtlpProtocol["HTTP_JSON"] = "http/json";
8
+ OtlpProtocol["GRPC"] = "grpc";
9
+ })(OtlpProtocol || (exports.OtlpProtocol = OtlpProtocol = {}));
10
+ //# sourceMappingURL=otlp-protocol.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp-protocol.enum.js","sourceRoot":"","sources":["../../src/enums/otlp-protocol.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,+CAA+B,CAAA;IAC/B,uCAAuB,CAAA;IACvB,6BAAa,CAAA;AACf,CAAC,EAJW,YAAY,4BAAZ,YAAY,QAIvB"}
@@ -0,0 +1,7 @@
1
+ export declare enum PropagatorType {
2
+ TRACE_CONTEXT = "tracecontext",
3
+ BAGGAGE = "baggage",
4
+ B3 = "b3",
5
+ B3_MULTI = "b3multi",
6
+ JAEGER = "jaeger"
7
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PropagatorType = void 0;
4
+ var PropagatorType;
5
+ (function (PropagatorType) {
6
+ PropagatorType["TRACE_CONTEXT"] = "tracecontext";
7
+ PropagatorType["BAGGAGE"] = "baggage";
8
+ PropagatorType["B3"] = "b3";
9
+ PropagatorType["B3_MULTI"] = "b3multi";
10
+ PropagatorType["JAEGER"] = "jaeger";
11
+ })(PropagatorType || (exports.PropagatorType = PropagatorType = {}));
12
+ //# sourceMappingURL=propagator-type.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propagator-type.enum.js","sourceRoot":"","sources":["../../src/enums/propagator-type.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,cAMX;AAND,WAAY,cAAc;IACxB,gDAA8B,CAAA;IAC9B,qCAAmB,CAAA;IACnB,2BAAS,CAAA;IACT,sCAAoB,CAAA;IACpB,mCAAiB,CAAA;AACnB,CAAC,EANW,cAAc,8BAAd,cAAc,QAMzB"}
@@ -0,0 +1,8 @@
1
+ export declare enum TelemetryErrorCode {
2
+ MISSING_SERVICE_NAME = "MISSING_SERVICE_NAME",
3
+ INVALID_SAMPLE_RATIO = "INVALID_SAMPLE_RATIO",
4
+ UNSUPPORTED_EXPORTER = "UNSUPPORTED_EXPORTER",
5
+ UNSUPPORTED_PROPAGATOR = "UNSUPPORTED_PROPAGATOR",
6
+ UNSUPPORTED_PROTOCOL = "UNSUPPORTED_PROTOCOL",
7
+ MISSING_OPTIONAL_DEPENDENCY = "MISSING_OPTIONAL_DEPENDENCY"
8
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelemetryErrorCode = void 0;
4
+ var TelemetryErrorCode;
5
+ (function (TelemetryErrorCode) {
6
+ TelemetryErrorCode["MISSING_SERVICE_NAME"] = "MISSING_SERVICE_NAME";
7
+ TelemetryErrorCode["INVALID_SAMPLE_RATIO"] = "INVALID_SAMPLE_RATIO";
8
+ TelemetryErrorCode["UNSUPPORTED_EXPORTER"] = "UNSUPPORTED_EXPORTER";
9
+ TelemetryErrorCode["UNSUPPORTED_PROPAGATOR"] = "UNSUPPORTED_PROPAGATOR";
10
+ TelemetryErrorCode["UNSUPPORTED_PROTOCOL"] = "UNSUPPORTED_PROTOCOL";
11
+ TelemetryErrorCode["MISSING_OPTIONAL_DEPENDENCY"] = "MISSING_OPTIONAL_DEPENDENCY";
12
+ })(TelemetryErrorCode || (exports.TelemetryErrorCode = TelemetryErrorCode = {}));
13
+ //# sourceMappingURL=telemetry-error-code.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-error-code.enum.js","sourceRoot":"","sources":["../../src/enums/telemetry-error-code.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,kBAOX;AAPD,WAAY,kBAAkB;IAC5B,mEAA6C,CAAA;IAC7C,mEAA6C,CAAA;IAC7C,mEAA6C,CAAA;IAC7C,uEAAiD,CAAA;IACjD,mEAA6C,CAAA;IAC7C,iFAA2D,CAAA;AAC7D,CAAC,EAPW,kBAAkB,kCAAlB,kBAAkB,QAO7B"}
@@ -0,0 +1,5 @@
1
+ export declare enum TelemetrySignal {
2
+ TRACES = "traces",
3
+ METRICS = "metrics",
4
+ LOGS = "logs"
5
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelemetrySignal = void 0;
4
+ var TelemetrySignal;
5
+ (function (TelemetrySignal) {
6
+ TelemetrySignal["TRACES"] = "traces";
7
+ TelemetrySignal["METRICS"] = "metrics";
8
+ TelemetrySignal["LOGS"] = "logs";
9
+ })(TelemetrySignal || (exports.TelemetrySignal = TelemetrySignal = {}));
10
+ //# sourceMappingURL=telemetry-signal.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-signal.enum.js","sourceRoot":"","sources":["../../src/enums/telemetry-signal.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,oCAAiB,CAAA;IACjB,sCAAmB,CAAA;IACnB,gCAAa,CAAA;AACf,CAAC,EAJW,eAAe,+BAAf,eAAe,QAI1B"}