spanport 0.1.0 → 0.1.1

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 (2) hide show
  1. package/README.md +115 -3
  2. package/package.json +13 -3
package/README.md CHANGED
@@ -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,126 @@ 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 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
+ ## What Spanport receives
59
+
60
+ | You write | Message | Level | Fields you can filter on |
61
+ | --- | --- | --- | --- |
62
+ | `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 |
63
+ | pino `logger.info({ "user.id": "u42" }, "Paid")` | `Paid` | pino's level (`fatal` is FATAL, `trace` is TRACE) | every other key, e.g. `user.id` |
64
+ | winston `logger.info("Paid", { "user.id": "u42" })` | `Paid` | winston's level (`http` is INFO, `verbose` is DEBUG) | every other key |
65
+
66
+ - **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.
67
+ - 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()`.
68
+ - Errors: `console.error(err)` and pino's `logger.error(err)` both keep the stack trace in the message.
69
+
70
+ ## Finding logs in Spanport
71
+
72
+ Pick the project and time range first; a search only looks inside that range.
73
+
74
+ - **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.
75
+ - **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.
76
+ - **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.
77
+ - Expand a line to see all its fields, with Filter and Exclude next to each.
78
+
79
+ ## Troubleshooting
80
+
81
+ ### Nothing arrives and there is no error
82
+
83
+ 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:
84
+
85
+ ```bash
86
+ curl -i -X POST http://localhost:4318/v1/logs -H "Content-Type: application/json" -d "{}"
87
+ ```
88
+
89
+ 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.
90
+
91
+ ### Searching for a name or id finds nothing
92
+
93
+ 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)).
94
+
95
+ ### NestJS errors show in the terminal but not in Spanport
96
+
97
+ 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:
98
+
99
+ ```ts
100
+ // src/common/logger/console-routed.logger.ts
101
+ import { LoggerService } from "@nestjs/common";
102
+
103
+ function asText(value: unknown): string {
104
+ if (value instanceof Error) return value.stack ?? value.message;
105
+ return typeof value === "string" ? value : JSON.stringify(value);
106
+ }
107
+
108
+ // Nest passes the context last; a value with line breaks is a stack trace, not a context.
109
+ function formatLine(message: unknown, optionalParams: unknown[]): string {
110
+ const lastParam = optionalParams.at(-1);
111
+ const context = typeof lastParam === "string" && !lastParam.includes("\n") ? lastParam : undefined;
112
+ const extraValues = context === undefined ? optionalParams : optionalParams.slice(0, -1);
113
+ const firstLine = context ? `[${context}] ${asText(message)}` : asText(message);
114
+ return [firstLine, ...extraValues.map(asText)].join("\n");
115
+ }
116
+
117
+ export class ConsoleRoutedLogger implements LoggerService {
118
+ log(message: unknown, ...params: unknown[]) { console.log(formatLine(message, params)); }
119
+ warn(message: unknown, ...params: unknown[]) { console.warn(formatLine(message, params)); }
120
+ error(message: unknown, ...params: unknown[]) { console.error(formatLine(message, params)); }
121
+ fatal(message: unknown, ...params: unknown[]) { console.error(formatLine(message, params)); }
122
+ debug(message: unknown, ...params: unknown[]) { console.debug(formatLine(message, params)); }
123
+ verbose(message: unknown, ...params: unknown[]) { console.debug(formatLine(message, params)); }
124
+ }
125
+ ```
126
+
127
+ ```ts
128
+ // main.ts
129
+ const app = await NestFactory.create(AppModule, { logger: new ConsoleRoutedLogger() });
130
+ ```
131
+
132
+ The same applies to any library that writes to `process.stdout` directly: give it a logger that uses `console`, pino or winston.
133
+
134
+ ### Failed requests show as INFO
135
+
136
+ 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:
137
+
138
+ ```ts
139
+ const writeLine = status >= 500 ? console.error : status >= 400 ? console.warn : console.log;
140
+ writeLine(`${method} ${url} ${status} ${durationMs}ms`);
141
+ ```
142
+
143
+ ### "data key ...xxxx was rejected"
144
+
145
+ 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:
146
+
147
+ ```bash
148
+ curl -i http://localhost:8000/api/v1/api-keys/current -H "Authorization: Bearer sp_ingest_..."
149
+ ```
150
+
151
+ `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.
152
+
153
+ ### Client IP and user are empty
154
+
155
+ 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).
156
+
157
+ ### "could not reach ... to check data key"
158
+
159
+ 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.
160
+
49
161
  ## Development
50
162
 
51
163
  ```bash
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "spanport",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
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"],
5
+ "keywords": [
6
+ "logging",
7
+ "observability",
8
+ "opentelemetry",
9
+ "otlp",
10
+ "spanport"
11
+ ],
6
12
  "license": "Apache-2.0",
7
13
  "author": "Nitish Bharadwaj <nitishbharadwaj2002@gmail.com>",
8
14
  "homepage": "https://github.com/span-port/spanport",
@@ -22,7 +28,11 @@
22
28
  "default": "./dist/index.js"
23
29
  }
24
30
  },
25
- "files": ["dist", "LICENSE", "README.md"],
31
+ "files": [
32
+ "dist",
33
+ "LICENSE",
34
+ "README.md"
35
+ ],
26
36
  "scripts": {
27
37
  "build": "tsc -p tsconfig.build.json",
28
38
  "typecheck": "tsc --noEmit -p tsconfig.json",