spanport 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # spanport (Node.js SDK)
2
2
 
3
- Send your Node.js app's logs to Spanport. Keep logging the way you already do.
3
+ Send your Node.js app's logs, and where each request spends its time, to Spanport. Keep logging the way you already do.
4
4
 
5
5
  ```bash
6
6
  npm install spanport
@@ -10,7 +10,7 @@ npm install spanport
10
10
  import * as spanport from "spanport";
11
11
 
12
12
  spanport.init({
13
- // From: python -m spanport_server.cli create-data-key
13
+ // From Setup → API keys → Data keys in Spanport (shown once)
14
14
  dataKey: "sp_ingest_...",
15
15
  service: "orders-api",
16
16
  // Defaults: http://localhost:4318 and http://localhost:8000
@@ -38,14 +38,156 @@ const logger = winston.createLogger({
38
38
  ```
39
39
 
40
40
  - **`console.debug/log/info/warn/error` are sent automatically**; what your app prints doesn't change. Turn this off with `captureConsole: false`.
41
- - **Logs are sent in the background**, in batches. A normal exit sends what is left; on `SIGTERM`, call `await spanport.shutdown()` first.
41
+ - **Logs are sent in the background**, in batches. A normal exit sends what is left; on `SIGTERM`, send it yourself: `process.on("SIGTERM", async () => { await spanport.shutdown(); process.exit(0); })`.
42
42
  - **Logs inside an OpenTelemetry span** carry its `trace_id` and `span_id`; so do pino lines that include them.
43
43
  - **Wrong key?** At start-up the SDK asks the server once whether the key is valid and prints a clear error on stderr if not. Pass `checkKey: false` if the app cannot reach the server.
44
44
  - **Use `https://` for anything beyond your own machine or Docker network.** The data key is sent with every batch.
45
- - **It never breaks your app.** `init()` never throws; problems are printed on stderr as `spanport: ...` and the app carries on.
45
+ - **It never breaks your app.** `init()` never throws; setup and key problems are printed on stderr as `spanport: ...` and the app carries on. An unreachable collector is not reported yet (see [Troubleshooting](#nothing-arrives-and-there-is-no-error)).
46
46
 
47
47
  Node.js 22.15 or newer. ES module; CommonJS apps can `require("spanport")`.
48
48
 
49
+ At start-up the SDK prints, on stderr:
50
+
51
+ ```
52
+ INFO spanport: sending logs and traces for service orders-api to http://localhost:4318
53
+ INFO spanport: data key ...a1b2 accepted
54
+ ```
55
+
56
+ If the second line is missing or says something else, see [Troubleshooting](#troubleshooting).
57
+
58
+ ## Request timing (traces)
59
+
60
+ `init()` also times each request your app handles and every step inside it, and sends them as traces; open **Requests** in Spanport to see them as a waterfall (request → controller → service → database / outgoing calls). Pass `traces: false` to send logs only.
61
+
62
+ - **Always on:** incoming HTTP requests and outgoing `http`/`https` calls. Outgoing calls carry a W3C `traceparent` header, so your other services running the SDK join the same trace (shown as a service call).
63
+ - **If your app has them installed**, also: route names for Express and NestJS (`npm install @opentelemetry/instrumentation-express`), Postgres queries (`@opentelemetry/instrumentation-pg`) and `fetch` calls (`@opentelemetry/instrumentation-undici`). On start-up the SDK says which it found: `spanport: also timing: ...`.
64
+ - **Call `init()` before your framework loads**, from a small file imported first, so Express and Postgres get timed:
65
+
66
+ ```ts
67
+ // src/spanport.ts
68
+ import * as spanport from "spanport";
69
+ spanport.init({ dataKey: process.env.SPANPORT_DATA_KEY ?? "", service: "kosha-backend" });
70
+
71
+ // src/main.ts: the very first import
72
+ import "./spanport";
73
+ import { NestFactory } from "@nestjs/core";
74
+ ```
75
+
76
+ **Your own code**: time every method of a controller or service, or any block:
77
+
78
+ ```ts
79
+ @spanport.trace() // "VariablesService.list", "VariablesService.find", ...
80
+ @Injectable()
81
+ export class VariablesService { ... }
82
+
83
+ const prompt = await spanport.withSpan("build prompt", () => render(template, data));
84
+ ```
85
+
86
+ Works with NestJS decorators (route paths and guards keep working) and with sync or async methods. A thrown error marks the step failed and is rethrown unchanged. Logs written during a step carry its trace, so **See logs** on a request in Spanport shows them.
87
+
88
+ ## What Spanport receives
89
+
90
+ | You write | Message | Level | Fields you can filter on |
91
+ | --- | --- | --- | --- |
92
+ | `console.log("Paid", { orderId: 7 })` | `Paid { orderId: 7 }` (as the terminal shows it) | `debug` DEBUG, `log`/`info` INFO, `warn` WARN, `error` ERROR | none: objects become part of the text |
93
+ | pino `logger.info({ "user.id": "u42" }, "Paid")` | `Paid` | pino's level (`fatal` is FATAL, `trace` is TRACE) | every other key, e.g. `user.id` |
94
+ | winston `logger.info("Paid", { "user.id": "u42" })` | `Paid` | winston's level (`http` is INFO, `verbose` is DEBUG) | every other key |
95
+
96
+ - **Use flat, dotted names for fields**: `{ "user.id": "u42" }`, not `{ user: { id: "u42" } }`. A nested object arrives as one field (`user`) holding JSON text, so the User ID filter can't see it.
97
+ - Spanport's **Client IP** and **User ID** filters read the fields `client.ip` and `user.id`. Every line also has `log.source` (`console` or `stream`) and the `service` you passed to `init()`.
98
+ - Errors: `console.error(err)` and pino's `logger.error(err)` both keep the stack trace in the message.
99
+
100
+ ## Finding logs in Spanport
101
+
102
+ Pick the project and time range first; a search only looks inside that range.
103
+
104
+ - **Message contains** finds any part of the text, ignoring case. `nitish` finds `GET /api/files 200 12ms user=nitish@acme.com`; `nitishbharadwaj` doesn't, because those letters never appear together in that line.
105
+ - **User ID is u42** and **Client IP is ...** work only for lines that carry the `user.id` / `client.ip` fields (pino or winston, see above). A user written inside the message text is found with Message contains instead.
106
+ - **Any other field** you send: choose "Type a query instead" and write `field:value`, e.g. `order.id:1042` or `tenant:acme*` (`*` is a wildcard). In a query, a plain word matches a whole word: `timeout` finds "request timeout" but not "timeouts"; use `timeout*` for that.
107
+ - Expand a line to see all its fields, with Filter and Exclude next to each.
108
+
109
+ ## Troubleshooting
110
+
111
+ ### Nothing arrives and there is no error
112
+
113
+ The SDK checks the data key, not the collector. If `collectorUrl` is wrong or the collector is down, batches are dropped **without a message**. Check that the collector answers from where the app runs:
114
+
115
+ ```bash
116
+ curl -i -X POST http://localhost:4318/v1/logs -H "Content-Type: application/json" -d "{}"
117
+ ```
118
+
119
+ Any HTTP answer (e.g. `200`) means it is reachable; "connection refused" means it isn't. An app running in Docker can't reach the host's `localhost`: use `http://host.docker.internal:4318`, or `http://collector:4318` on the Spanport Compose network. Also check the Logs page shows the right project and a time range that includes now.
120
+
121
+ ### Searching for a name or id finds nothing
122
+
123
+ Search finds only text that is in the line. Expand a line you expect to match and look at its message and fields: if the user appears as `user=nitish@acme.com` inside the message, search **Message contains** `nitish@acme.com`; to use the **User ID** filter, send `user.id` as a field (see [What Spanport receives](#what-spanport-receives)).
124
+
125
+ ### NestJS errors show in the terminal but not in Spanport
126
+
127
+ Nest's built-in logger writes straight to `process.stdout`/`process.stderr`, not through `console`, so the SDK never sees its lines. That includes `ERROR [ExceptionsHandler] ...` for every unhandled exception. Route Nest's logger through `console` and they arrive with the right level (and stack trace), while the terminal output stays the same:
128
+
129
+ ```ts
130
+ // src/common/logger/console-routed.logger.ts
131
+ import { LoggerService } from "@nestjs/common";
132
+
133
+ function asText(value: unknown): string {
134
+ if (value instanceof Error) return value.stack ?? value.message;
135
+ return typeof value === "string" ? value : JSON.stringify(value);
136
+ }
137
+
138
+ // Nest passes the context last; a value with line breaks is a stack trace, not a context.
139
+ function formatLine(message: unknown, optionalParams: unknown[]): string {
140
+ const lastParam = optionalParams.at(-1);
141
+ const context = typeof lastParam === "string" && !lastParam.includes("\n") ? lastParam : undefined;
142
+ const extraValues = context === undefined ? optionalParams : optionalParams.slice(0, -1);
143
+ const firstLine = context ? `[${context}] ${asText(message)}` : asText(message);
144
+ return [firstLine, ...extraValues.map(asText)].join("\n");
145
+ }
146
+
147
+ export class ConsoleRoutedLogger implements LoggerService {
148
+ log(message: unknown, ...params: unknown[]) { console.log(formatLine(message, params)); }
149
+ warn(message: unknown, ...params: unknown[]) { console.warn(formatLine(message, params)); }
150
+ error(message: unknown, ...params: unknown[]) { console.error(formatLine(message, params)); }
151
+ fatal(message: unknown, ...params: unknown[]) { console.error(formatLine(message, params)); }
152
+ debug(message: unknown, ...params: unknown[]) { console.debug(formatLine(message, params)); }
153
+ verbose(message: unknown, ...params: unknown[]) { console.debug(formatLine(message, params)); }
154
+ }
155
+ ```
156
+
157
+ ```ts
158
+ // main.ts
159
+ const app = await NestFactory.create(AppModule, { logger: new ConsoleRoutedLogger() });
160
+ ```
161
+
162
+ The same applies to any library that writes to `process.stdout` directly: give it a logger that uses `console`, pino or winston.
163
+
164
+ ### Failed requests show as INFO
165
+
166
+ Every `console.log` line is INFO, whatever its text says. A request logger should pick the method by status, so the ERROR and WARN cards on the Logs page count failed calls:
167
+
168
+ ```ts
169
+ const writeLine = status >= 500 ? console.error : status >= 400 ? console.warn : console.log;
170
+ writeLine(`${method} ${url} ${status} ${durationMs}ms`);
171
+ ```
172
+
173
+ ### "data key ...xxxx was rejected"
174
+
175
+ The server doesn't know the key: it was mistyped, revoked, deleted, belongs to an archived project, or is an admin key (`sp_admin_...`) instead of a data key (`sp_ingest_...`). Check it with:
176
+
177
+ ```bash
178
+ curl -i http://localhost:8000/api/v1/api-keys/current -H "Authorization: Bearer sp_ingest_..."
179
+ ```
180
+
181
+ `200` means the key is fine; `401` means create a new data key on the API keys page (it is shown once) and restart the app with it.
182
+
183
+ ### Client IP and user are empty
184
+
185
+ The SDK sends only what a log line carries. Add them as fields named `client.ip` and `user.id`, e.g. with pino: `logger.error({ "client.ip": req.ip, "user.id": userId }, "Payment failed")`. Behind a proxy, read the client address from `X-Forwarded-For` (`app.set("trust proxy", true)` in Express).
186
+
187
+ ### "could not reach ... to check data key"
188
+
189
+ The key check couldn't reach `serverUrl` (default `http://localhost:8000`). Logs are still sent to the collector, but nobody has confirmed the key. Point `serverUrl` at the Spanport server, or pass `checkKey: false` if the app has no route to it.
190
+
49
191
  ## Development
50
192
 
51
193
  ```bash
@@ -3,6 +3,22 @@ export declare const DATA_KEY_PREFIX = "sp_ingest_";
3
3
  export declare const DEFAULT_COLLECTOR_URL = "http://localhost:4318";
4
4
  export declare const DEFAULT_SERVER_URL = "http://localhost:8000";
5
5
  export declare const OTLP_LOGS_PATH = "/v1/logs";
6
+ export declare const OTLP_TRACES_PATH = "/v1/traces";
7
+ /**
8
+ * Instrumentations switched on only when the app has installed them (they patch libraries the
9
+ * SDK doesn't depend on): Express and NestJS route names, Postgres queries, fetch calls.
10
+ */
11
+ export declare const OPTIONAL_INSTRUMENTATIONS: readonly {
12
+ module: string;
13
+ exportName: string;
14
+ }[];
15
+ /** Node built-ins the HTTP instrumentation patches; required once when tracing starts. */
16
+ export declare const PATCHED_BUILTINS: readonly string[];
17
+ /** Separates a decorated class's name from its method in span names, e.g. "UsersService.find". */
18
+ export declare const SPAN_NAME_SEPARATOR = ".";
19
+ export declare const CONSTRUCTOR_NAME = "constructor";
20
+ /** The name a class decorator falls back to for an anonymous class. */
21
+ export declare const ANONYMOUS_CLASS_NAME = "AnonymousClass";
6
22
  export declare const CURRENT_API_KEY_PATH = "/api/v1/api-keys/current";
7
23
  export declare const AUTHORIZATION_HEADER = "Authorization";
8
24
  export declare const BEARER_PREFIX = "Bearer ";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAC/D,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAwB,CAAC;AAC5E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,oDAAqD,CAAC;AAE3F,mDAAmD;AACnD,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAE7B,eAAO,MAAM,sBAAsB,iBAAiB,CAAC;AACrD,oEAAoE;AACpE,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAE9C,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,cAAc,YAAY,CAAC;AACxC,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,mGAAmG;AACnG,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAAuB,CAAC;AACpE,eAAO,MAAM,WAAW,UAAU,CAAC;AACnC,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAA0B,CAAC;AACpE,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,cAAc,OAAO,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAItF,CAAC;AACF,0FAA0F;AAC1F,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAgC,CAAC;AAC/E,kGAAkG;AAClG,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,uEAAuE;AACvE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AACrD,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAC/D,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAwB,CAAC;AAC5E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,oDAAqD,CAAC;AAE3F,mDAAmD;AACnD,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAE7B,eAAO,MAAM,sBAAsB,iBAAiB,CAAC;AACrD,oEAAoE;AACpE,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAE9C,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,cAAc,YAAY,CAAC;AACxC,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,mGAAmG;AACnG,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAAuB,CAAC;AACpE,eAAO,MAAM,WAAW,UAAU,CAAC;AACnC,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAA0B,CAAC;AACpE,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,cAAc,OAAO,CAAC"}
package/dist/constants.js CHANGED
@@ -3,6 +3,23 @@ export const DATA_KEY_PREFIX = "sp_ingest_";
3
3
  export const DEFAULT_COLLECTOR_URL = "http://localhost:4318";
4
4
  export const DEFAULT_SERVER_URL = "http://localhost:8000";
5
5
  export const OTLP_LOGS_PATH = "/v1/logs";
6
+ export const OTLP_TRACES_PATH = "/v1/traces";
7
+ /**
8
+ * Instrumentations switched on only when the app has installed them (they patch libraries the
9
+ * SDK doesn't depend on): Express and NestJS route names, Postgres queries, fetch calls.
10
+ */
11
+ export const OPTIONAL_INSTRUMENTATIONS = [
12
+ { module: "@opentelemetry/instrumentation-express", exportName: "ExpressInstrumentation" },
13
+ { module: "@opentelemetry/instrumentation-pg", exportName: "PgInstrumentation" },
14
+ { module: "@opentelemetry/instrumentation-undici", exportName: "UndiciInstrumentation" },
15
+ ];
16
+ /** Node built-ins the HTTP instrumentation patches; required once when tracing starts. */
17
+ export const PATCHED_BUILTINS = ["node:http", "node:https"];
18
+ /** Separates a decorated class's name from its method in span names, e.g. "UsersService.find". */
19
+ export const SPAN_NAME_SEPARATOR = ".";
20
+ export const CONSTRUCTOR_NAME = "constructor";
21
+ /** The name a class decorator falls back to for an anonymous class. */
22
+ export const ANONYMOUS_CLASS_NAME = "AnonymousClass";
6
23
  export const CURRENT_API_KEY_PATH = "/api/v1/api-keys/current";
7
24
  export const AUTHORIZATION_HEADER = "Authorization";
8
25
  export const BEARER_PREFIX = "Bearer ";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC;AAC5C,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AACvC,MAAM,CAAC,MAAM,qBAAqB,GAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAE3F,mDAAmD;AACnD,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,sBAAsB,GAAG,cAAc,CAAC;AACrD,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,yEAAyE;AACzE,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAE9C,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AACjD,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AACxC,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAE1C,mGAAmG;AACnG,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAsB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC;AAC5C,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsD;IAC1F,EAAE,MAAM,EAAE,wCAAwC,EAAE,UAAU,EAAE,wBAAwB,EAAE;IAC1F,EAAE,MAAM,EAAE,mCAAmC,EAAE,UAAU,EAAE,mBAAmB,EAAE;IAChF,EAAE,MAAM,EAAE,uCAAuC,EAAE,UAAU,EAAE,uBAAuB,EAAE;CACzF,CAAC;AACF,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gBAAgB,GAAsB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAC/E,kGAAkG;AAClG,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAC9C,uEAAuE;AACvE,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AACrD,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AACvC,MAAM,CAAC,MAAM,qBAAqB,GAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAE3F,mDAAmD;AACnD,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,sBAAsB,GAAG,cAAc,CAAC;AACrD,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,yEAAyE;AACzE,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAE9C,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AACjD,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AACxC,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAE1C,mGAAmG;AACnG,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAsB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Spanport SDK: send your Node.js app's logs to Spanport.
2
+ * Spanport SDK: send your Node.js app's logs and traces (request timings) to Spanport.
3
3
  *
4
4
  * import * as spanport from "spanport";
5
5
  * spanport.init({ dataKey: "sp_ingest_...", service: "orders-api" });
@@ -21,7 +21,10 @@ export interface SpanportOptions {
21
21
  checkKey?: boolean;
22
22
  /** Set false to stop copying console.* calls to Spanport. Default true. */
23
23
  captureConsole?: boolean;
24
+ /** Set false to send no traces (request timings). Default true. */
25
+ traces?: boolean;
24
26
  }
27
+ export { trace, withSpan } from "./spans.js";
25
28
  /**
26
29
  * Start sending this app's logs to Spanport. Call once at start-up. Never throws.
27
30
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAqB5C,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oGAAoG;IACpG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAWD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAMnD;AAED;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAU9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,QAAQ,CAIpC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAsB5C,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oGAAoG;IACpG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AASD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK7C;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAMnD;AAED;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAU9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,QAAQ,CAIpC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Spanport SDK: send your Node.js app's logs to Spanport.
2
+ * Spanport SDK: send your Node.js app's logs and traces (request timings) to Spanport.
3
3
  *
4
4
  * import * as spanport from "spanport";
5
5
  * spanport.init({ dataKey: "sp_ingest_...", service: "orders-api" });
@@ -17,7 +17,9 @@ import { createLogStream } from "./logStream.js";
17
17
  import { buildLoggerProvider, buildOtlpExporter, toLogRecord } from "./pipeline.js";
18
18
  import { sdkError, sdkInfo, sdkWarning } from "./sdkMessages.js";
19
19
  import { consoleSeverity } from "./severity.js";
20
+ import { buildTraceExporter, startTracing } from "./tracing.js";
20
21
  import { isHttpUrl } from "./urls.js";
22
+ export { trace, withSpan } from "./spans.js";
21
23
  let active;
22
24
  let exitFlushInstalled = false;
23
25
  /**
@@ -44,7 +46,7 @@ export async function shutdown() {
44
46
  return;
45
47
  pipeline.restoreConsole?.();
46
48
  try {
47
- await pipeline.provider.shutdown();
49
+ await Promise.all([pipeline.provider.shutdown(), pipeline.tracing?.stop()]);
48
50
  }
49
51
  catch (error) {
50
52
  sdkWarning(`shutdown failed; some queued logs may not have been sent: ${describe(error)}`);
@@ -82,11 +84,20 @@ function start(options) {
82
84
  : captureConsole((method, message) => {
83
85
  logger.emit(toLogRecord({ body: message, severity: consoleSeverity(method), attributes: {} }, CONSOLE_SOURCE));
84
86
  });
85
- active = { provider, logger, restoreConsole };
87
+ const serverUrl = options.serverUrl ?? DEFAULT_SERVER_URL;
88
+ const tracing = options.traces === false
89
+ ? undefined
90
+ : startTracing(options.service, buildTraceExporter(collectorUrl, options.dataKey), {
91
+ collectorUrl,
92
+ serverUrl: isHttpUrl(serverUrl) ? serverUrl : collectorUrl,
93
+ });
94
+ active = { provider, logger, restoreConsole, tracing };
86
95
  installExitFlush();
87
- sdkInfo(`sending logs for service ${options.service} to ${collectorUrl}`);
96
+ sdkInfo(`sending logs${tracing ? " and traces" : ""} for service ${options.service} to ${collectorUrl}`);
97
+ if (tracing?.optional.length)
98
+ sdkInfo(`also timing: ${tracing.optional.join(", ")}`);
88
99
  if (options.checkKey !== false) {
89
- void checkDataKey(options.serverUrl ?? DEFAULT_SERVER_URL, options.dataKey);
100
+ void checkDataKey(serverUrl, options.dataKey);
90
101
  }
91
102
  }
92
103
  /** Send what is queued when the app exits normally. Installed once, however often init runs. */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Your own steps in a trace: `withSpan(name, fn)` for any block, and the `trace()` class decorator
3
+ * that times every method of a controller or service as "ClassName.method".
4
+ */
5
+ /**
6
+ * Run `work` inside a span named `name`, nested under the current one. Works for sync and async
7
+ * work; a thrown error or rejected promise marks the span failed and is rethrown unchanged.
8
+ *
9
+ * const user = await spanport.withSpan("load user", () => repo.find(id));
10
+ *
11
+ * @returns Whatever `work` returns.
12
+ */
13
+ export declare function withSpan<T>(name: string, work: () => T): T;
14
+ /**
15
+ * Class decorator: every method becomes a step of the trace, named "ClassName.method".
16
+ *
17
+ * @spanport.trace()
18
+ * @Injectable()
19
+ * export class VariablesService { ... }
20
+ *
21
+ * Works with TypeScript's legacy decorators (NestJS) and standard ones. Method metadata (e.g.
22
+ * NestJS route paths) is copied to the timed method, so frameworks keep working.
23
+ *
24
+ * @param name - The name used before the dot; the class's own name by default.
25
+ */
26
+ export declare function trace(name?: string): (target: abstract new (...args: never[]) => unknown) => void;
27
+ //# sourceMappingURL=spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.d.ts","sourceRoot":"","sources":["../src/spans.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoBH;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAuB1D;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,IAEzB,QAAQ,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,KAAG,IAAI,CAUlE"}
package/dist/spans.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Your own steps in a trace: `withSpan(name, fn)` for any block, and the `trace()` class decorator
3
+ * that times every method of a controller or service as "ClassName.method".
4
+ */
5
+ import { SpanStatusCode, trace as otelTrace } from "@opentelemetry/api";
6
+ import { ANONYMOUS_CLASS_NAME, CONSTRUCTOR_NAME, SDK_SCOPE_NAME, SPAN_NAME_SEPARATOR, } from "./constants.js";
7
+ /**
8
+ * Run `work` inside a span named `name`, nested under the current one. Works for sync and async
9
+ * work; a thrown error or rejected promise marks the span failed and is rethrown unchanged.
10
+ *
11
+ * const user = await spanport.withSpan("load user", () => repo.find(id));
12
+ *
13
+ * @returns Whatever `work` returns.
14
+ */
15
+ export function withSpan(name, work) {
16
+ return otelTrace.getTracer(SDK_SCOPE_NAME).startActiveSpan(name, (span) => {
17
+ try {
18
+ const result = work();
19
+ if (result instanceof Promise) {
20
+ return result.then((value) => {
21
+ span.end();
22
+ return value;
23
+ }, (error) => {
24
+ fail(span, error);
25
+ throw error;
26
+ });
27
+ }
28
+ span.end();
29
+ return result;
30
+ }
31
+ catch (error) {
32
+ fail(span, error);
33
+ throw error;
34
+ }
35
+ });
36
+ }
37
+ function fail(span, error) {
38
+ span.recordException(error instanceof Error ? error : String(error));
39
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) });
40
+ span.end();
41
+ }
42
+ /**
43
+ * Class decorator: every method becomes a step of the trace, named "ClassName.method".
44
+ *
45
+ * @spanport.trace()
46
+ * @Injectable()
47
+ * export class VariablesService { ... }
48
+ *
49
+ * Works with TypeScript's legacy decorators (NestJS) and standard ones. Method metadata (e.g.
50
+ * NestJS route paths) is copied to the timed method, so frameworks keep working.
51
+ *
52
+ * @param name - The name used before the dot; the class's own name by default.
53
+ */
54
+ export function trace(name) {
55
+ // Standard decorators also pass a context object; it isn't needed, so it isn't declared.
56
+ return (target) => {
57
+ const className = name ?? (target.name || ANONYMOUS_CLASS_NAME);
58
+ const prototype = target.prototype;
59
+ for (const methodName of Object.getOwnPropertyNames(prototype)) {
60
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
61
+ if (methodName === CONSTRUCTOR_NAME || typeof descriptor?.value !== "function")
62
+ continue;
63
+ const timed = timedMethod(`${className}${SPAN_NAME_SEPARATOR}${methodName}`, descriptor.value);
64
+ Object.defineProperty(prototype, methodName, { ...descriptor, value: timed });
65
+ }
66
+ };
67
+ }
68
+ /** A method that runs the original inside a span, carrying over its name and metadata. */
69
+ function timedMethod(spanName, original) {
70
+ const timed = function (...args) {
71
+ return withSpan(spanName, () => original.apply(this, args));
72
+ };
73
+ Object.defineProperty(timed, "name", { value: original.name });
74
+ copyMetadata(original, timed);
75
+ return timed;
76
+ }
77
+ /** Copy reflect-metadata entries (NestJS route paths, guards...) from one function to another. */
78
+ function copyMetadata(from, to) {
79
+ const reflect = Reflect;
80
+ if (!reflect.getMetadataKeys || !reflect.getMetadata || !reflect.defineMetadata)
81
+ return;
82
+ for (const key of reflect.getMetadataKeys(from)) {
83
+ reflect.defineMetadata(key, reflect.getMetadata(key, from), to);
84
+ }
85
+ }
86
+ //# sourceMappingURL=spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.js","sourceRoot":"","sources":["../src/spans.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAa,cAAc,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAWxB;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAI,IAAY,EAAE,IAAa;IACrD,OAAO,SAAS,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;QACxE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;YACtB,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;gBAC9B,OAAO,MAAM,CAAC,IAAI,CAChB,CAAC,KAAc,EAAE,EAAE;oBACjB,IAAI,CAAC,GAAG,EAAE,CAAC;oBACX,OAAO,KAAK,CAAC;gBACf,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;oBACjB,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAClB,MAAM,KAAK,CAAC;gBACd,CAAC,CACG,CAAC;YACT,CAAC;YACD,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAClB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,IAAU,EAAE,KAAc;IACtC,IAAI,CAAC,eAAe,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChH,IAAI,CAAC,GAAG,EAAE,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,IAAa;IACjC,yFAAyF;IACzF,OAAO,CAAC,MAAkD,EAAQ,EAAE;QAClE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,oBAAoB,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,MAAM,CAAC,SAAoC,CAAC;QAC9D,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;YAC1E,IAAI,UAAU,KAAK,gBAAgB,IAAI,OAAO,UAAU,EAAE,KAAK,KAAK,UAAU;gBAAE,SAAS;YACzF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,SAAS,GAAG,mBAAmB,GAAG,UAAU,EAAE,EAAE,UAAU,CAAC,KAAoB,CAAC,CAAC;YAC9G,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,SAAS,WAAW,CAAC,QAAgB,EAAE,QAAqB;IAC1D,MAAM,KAAK,GAAG,UAAyB,GAAG,IAAe;QACvD,OAAO,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC;IACF,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kGAAkG;AAClG,SAAS,YAAY,CAAC,IAAiB,EAAE,EAAe;IACtD,MAAM,OAAO,GAAG,OAAqC,CAAC;IACtD,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,cAAc;QAAE,OAAO;IACxF,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Traces: each request the app handles, and each step inside it (outgoing HTTP, database calls,
3
+ * decorated controllers and services), sent in the background to the collector's /v1/traces.
4
+ */
5
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
6
+ import { type SpanExporter, TracerProvider } from "@opentelemetry/sdk-trace";
7
+ export interface ActiveTracing {
8
+ provider: TracerProvider;
9
+ /** Names of the optional instrumentations the app had installed. */
10
+ optional: string[];
11
+ /** Send what is queued, then stop tracing and put the globals back. */
12
+ stop: () => Promise<void>;
13
+ }
14
+ /** Hosts the SDK itself talks to; requests to them are never traced, so Spanport never traces itself. */
15
+ export interface OwnHosts {
16
+ collectorUrl: string;
17
+ serverUrl: string;
18
+ }
19
+ /**
20
+ * Exporter that sends span batches to the collector with the data key as a bearer token.
21
+ *
22
+ * @param collectorUrl - Base URL of the OTel Collector, e.g. "http://localhost:4318".
23
+ * @param dataKey - The raw data key.
24
+ */
25
+ export declare function buildTraceExporter(collectorUrl: string, dataKey: string): OTLPTraceExporter;
26
+ /**
27
+ * Start tracing: keep the current request across awaits, register the tracer provider and switch
28
+ * on HTTP timing plus any optional instrumentation the app has installed.
29
+ *
30
+ * @param service - Service name shown in Spanport.
31
+ * @param exporter - Where span batches go; OTLP in real use, in-memory in tests.
32
+ * @param ownHosts - The SDK's own collector and server, never traced.
33
+ * @param loadModule - Loads an optional instrumentation by name; tests pass their own.
34
+ * @returns The running tracing, and how to stop it.
35
+ */
36
+ export declare function startTracing(service: string, exporter: SpanExporter, ownHosts: OwnHosts, loadModule?: (name: string) => unknown): ActiveTracing;
37
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAI5E,OAAO,EAAsB,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAYjG,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,CAAC;IACzB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,uEAAuE;IACvE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,yGAAyG;AACzG,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAK3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,EAClB,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAA0B,GACvD,aAAa,CA8Bf"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Traces: each request the app handles, and each step inside it (outgoing HTTP, database calls,
3
+ * decorated controllers and services), sent in the background to the collector's /v1/traces.
4
+ */
5
+ import { createRequire } from "node:module";
6
+ import { context, propagation, trace } from "@opentelemetry/api";
7
+ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
8
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
10
+ import { registerInstrumentations } from "@opentelemetry/instrumentation";
11
+ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
12
+ import { resourceFromAttributes } from "@opentelemetry/resources";
13
+ import { BatchSpanProcessor, TracerProvider } from "@opentelemetry/sdk-trace";
14
+ import { AUTHORIZATION_HEADER, BEARER_PREFIX, OPTIONAL_INSTRUMENTATIONS, OTLP_TRACES_PATH, PATCHED_BUILTINS, SERVICE_NAME_ATTRIBUTE, } from "./constants.js";
15
+ import { joinUrl } from "./urls.js";
16
+ /**
17
+ * Exporter that sends span batches to the collector with the data key as a bearer token.
18
+ *
19
+ * @param collectorUrl - Base URL of the OTel Collector, e.g. "http://localhost:4318".
20
+ * @param dataKey - The raw data key.
21
+ */
22
+ export function buildTraceExporter(collectorUrl, dataKey) {
23
+ return new OTLPTraceExporter({
24
+ url: joinUrl(collectorUrl, OTLP_TRACES_PATH),
25
+ headers: { [AUTHORIZATION_HEADER]: BEARER_PREFIX + dataKey },
26
+ });
27
+ }
28
+ /**
29
+ * Start tracing: keep the current request across awaits, register the tracer provider and switch
30
+ * on HTTP timing plus any optional instrumentation the app has installed.
31
+ *
32
+ * @param service - Service name shown in Spanport.
33
+ * @param exporter - Where span batches go; OTLP in real use, in-memory in tests.
34
+ * @param ownHosts - The SDK's own collector and server, never traced.
35
+ * @param loadModule - Loads an optional instrumentation by name; tests pass their own.
36
+ * @returns The running tracing, and how to stop it.
37
+ */
38
+ export function startTracing(service, exporter, ownHosts, loadModule = requireInstalled) {
39
+ const contextManager = new AsyncLocalStorageContextManager().enable();
40
+ context.setGlobalContextManager(contextManager);
41
+ // Outgoing calls carry a W3C traceparent header, so another service running the SDK joins
42
+ // the same trace (Spanport then shows the call as a service call).
43
+ propagation.setGlobalPropagator(new W3CTraceContextPropagator());
44
+ const provider = new TracerProvider({
45
+ resource: resourceFromAttributes({ [SERVICE_NAME_ATTRIBUTE]: service }),
46
+ spanProcessors: [new BatchSpanProcessor({ exporter })],
47
+ });
48
+ trace.setGlobalTracerProvider(provider);
49
+ useHttpInstrumentation(provider, ownHosts);
50
+ const optional = loadOptionalInstrumentations(loadModule);
51
+ const unregisterOptional = registerInstrumentations({
52
+ tracerProvider: provider,
53
+ instrumentations: optional.map((loaded) => loaded.instrumentation),
54
+ });
55
+ return {
56
+ provider,
57
+ optional: optional.map((loaded) => loaded.module),
58
+ stop: async () => {
59
+ unregisterOptional();
60
+ // The HTTP instrumentation stays on (see useHttpInstrumentation); with its provider shut
61
+ // down, the spans it makes go nowhere.
62
+ await provider.shutdown();
63
+ trace.disable();
64
+ context.disable();
65
+ propagation.disable();
66
+ },
67
+ };
68
+ }
69
+ // The HTTP instrumentation is switched on once per process and stays on. Node's http module is
70
+ // patched when it is require()d, which happens once; switched off, the instrumentation can't
71
+ // patch it again, so a later init() (after shutdown()) would time nothing.
72
+ let httpInstrumentation;
73
+ /**
74
+ * Time incoming requests and outgoing http/https calls (except the SDK's own) into `provider`.
75
+ */
76
+ function useHttpInstrumentation(provider, ownHosts) {
77
+ const ownOrigins = new Set([new URL(ownHosts.collectorUrl).host, new URL(ownHosts.serverUrl).host]);
78
+ const config = {
79
+ ignoreOutgoingRequestHook: (request) => ownOrigins.has(outgoingHost(request)),
80
+ };
81
+ if (httpInstrumentation) {
82
+ httpInstrumentation.setConfig(config);
83
+ httpInstrumentation.setTracerProvider(provider);
84
+ return;
85
+ }
86
+ httpInstrumentation = new HttpInstrumentation(config);
87
+ registerInstrumentations({ tracerProvider: provider, instrumentations: [httpInstrumentation] });
88
+ // Node's http/https are usually loaded already, and ESM imports never go through require, so
89
+ // require them once now: that patches the shared module object ESM and CommonJS code both use.
90
+ for (const builtin of PATCHED_BUILTINS)
91
+ requireFromApp(builtin);
92
+ }
93
+ /** "host:port" of an outgoing request, as URL.host writes it (no port for the default one). */
94
+ function outgoingHost(request) {
95
+ const host = request.hostname ?? request.host ?? "";
96
+ return request.port ? `${host}:${String(request.port)}` : host;
97
+ }
98
+ /** The optional instrumentations the app has installed; anything missing is skipped quietly. */
99
+ function loadOptionalInstrumentations(loadModule) {
100
+ const loaded = [];
101
+ for (const { module, exportName } of OPTIONAL_INSTRUMENTATIONS) {
102
+ const instrumentation = construct(loadModule(module), exportName);
103
+ if (instrumentation)
104
+ loaded.push({ module, instrumentation });
105
+ }
106
+ return loaded;
107
+ }
108
+ /** `new exports[exportName]()`, or undefined when the module or the class isn't there. */
109
+ function construct(moduleExports, exportName) {
110
+ if (typeof moduleExports !== "object" || moduleExports === null)
111
+ return undefined;
112
+ const exported = moduleExports[exportName];
113
+ if (typeof exported !== "function")
114
+ return undefined;
115
+ return new exported();
116
+ }
117
+ const requireFromApp = createRequire(import.meta.url);
118
+ /** The module if the app has it installed, else undefined. */
119
+ function requireInstalled(name) {
120
+ try {
121
+ return requireFromApp(name);
122
+ }
123
+ catch {
124
+ // Not installed: that instrumentation is simply off.
125
+ return undefined;
126
+ }
127
+ }
128
+ //# sourceMappingURL=tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "spanport",
3
- "version": "0.1.0",
4
- "description": "Send your Node.js app's logs to Spanport. Keep logging the way you already do.",
5
- "keywords": ["logging", "observability", "opentelemetry", "otlp", "spanport"],
3
+ "version": "1.0.0",
4
+ "description": "Send your Node.js app's logs and request traces to Spanport. Keep logging the way you already do.",
5
+ "keywords": [
6
+ "logging",
7
+ "tracing",
8
+ "observability",
9
+ "opentelemetry",
10
+ "otlp",
11
+ "spanport"
12
+ ],
6
13
  "license": "Apache-2.0",
7
14
  "author": "Nitish Bharadwaj <nitishbharadwaj2002@gmail.com>",
8
- "homepage": "https://github.com/span-port/spanport",
15
+ "homepage": "https://github.com/span-port/spanport/tree/sdk-node-v1.0.0/sdk/node#readme",
9
16
  "repository": {
10
17
  "type": "git",
11
18
  "url": "git+https://github.com/span-port/spanport.git",
@@ -22,7 +29,11 @@
22
29
  "default": "./dist/index.js"
23
30
  }
24
31
  },
25
- "files": ["dist", "LICENSE", "README.md"],
32
+ "files": [
33
+ "dist",
34
+ "LICENSE",
35
+ "README.md"
36
+ ],
26
37
  "scripts": {
27
38
  "build": "tsc -p tsconfig.build.json",
28
39
  "typecheck": "tsc --noEmit -p tsconfig.json",
@@ -32,14 +43,18 @@
32
43
  },
33
44
  "dependencies": {
34
45
  "@opentelemetry/api": ">=1.10.0 <1.11.0",
46
+ "@opentelemetry/context-async-hooks": ">=3.0.0 <4.0.0",
47
+ "@opentelemetry/core": ">=3.0.0 <4.0.0",
35
48
  "@opentelemetry/exporter-logs-otlp-http": ">=0.300.0 <0.301.0",
49
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.300.0 <0.301.0",
50
+ "@opentelemetry/instrumentation": ">=0.300.0 <0.301.0",
51
+ "@opentelemetry/instrumentation-http": ">=0.300.0 <0.301.0",
36
52
  "@opentelemetry/resources": ">=3.0.0 <4.0.0",
37
- "@opentelemetry/sdk-logs": ">=3.0.0 <4.0.0"
53
+ "@opentelemetry/sdk-logs": ">=3.0.0 <4.0.0",
54
+ "@opentelemetry/sdk-trace": ">=3.0.0 <4.0.0"
38
55
  },
39
56
  "devDependencies": {
40
57
  "@eslint/js": "10.0.1",
41
- "@opentelemetry/context-async-hooks": "3.0.0",
42
- "@opentelemetry/sdk-trace": "3.0.0",
43
58
  "@types/node": "22.20.5",
44
59
  "eslint": "10.13.0",
45
60
  "typescript": "6.0.3",