@bymax-one/nest-logger 1.0.4 → 1.0.5
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/CHANGELOG.md +67 -1
- package/README.md +28 -11
- package/package.json +16 -15
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,71 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.0.5] - 2026-07-30
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **Peer floors raised to exclude known-vulnerable NestJS versions.** `@nestjs/common ^11.0.0`
|
|
19
|
+
admitted 11.0.0–11.0.15, carrying [GHSA-cj7v-w2c7-cp7c](https://github.com/advisories/GHSA-cj7v-w2c7-cp7c)
|
|
20
|
+
(remote code execution via the `Content-Type` header, patched in 11.0.16), and
|
|
21
|
+
`@nestjs/core ^11.0.0` admitted everything up to 11.1.17, carrying
|
|
22
|
+
[GHSA-36xv-jgw5-4q75](https://github.com/advisories/GHSA-36xv-jgw5-4q75) (patched in 11.1.18).
|
|
23
|
+
|
|
24
|
+
A peer range states which versions this library supports. A floor below a published
|
|
25
|
+
advisory told a consumer a vulnerable install was supported, and nothing in their
|
|
26
|
+
tooling contradicted it — the install resolved cleanly and silently. Floors are now
|
|
27
|
+
`^11.0.16` and `^11.1.18`.
|
|
28
|
+
|
|
29
|
+
Shipped as a patch, which is where a security fix belongs; a minor would reach the
|
|
30
|
+
same installs anyway, since `^1.0.4` accepts `1.1.0` as readily as `1.0.5`. No
|
|
31
|
+
runtime behaviour changed.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **The custom-destination examples did not compile against `ILogDestination`.**
|
|
36
|
+
The published interface takes a serialized line:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
write(payload: string): void | Promise<void> // newline-terminated JSON, UTF-8
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
All three examples in the README — `LokiDestination`, `PrismaLogDestination`,
|
|
43
|
+
`RollingFileDestination` — and the API reference table declared
|
|
44
|
+
`write(entry: LogEntry): void`, so anyone following the README to build a
|
|
45
|
+
destination got:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
TS2416: Property 'write' in type 'LokiDestination' is not assignable to the
|
|
49
|
+
same property in base type 'ILogDestination'.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`ILogDestination` is this library's extension point, so the documentation was
|
|
53
|
+
teaching the wrong shape for the one thing consumers are meant to implement.
|
|
54
|
+
`RollingFileDestination` compounded it by re-serializing a payload that already
|
|
55
|
+
**is** newline-terminated JSON — anyone casting past the type error would have
|
|
56
|
+
written double-encoded lines to disk.
|
|
57
|
+
|
|
58
|
+
Each example now does what its own job requires rather than all three doing the
|
|
59
|
+
same thing: Loki buffers the line verbatim, Prisma parses because it stores
|
|
60
|
+
individual columns, rolling-file writes it through untouched.
|
|
61
|
+
|
|
62
|
+
- **The request-id middleware typed `req` as the global DOM `Request`**, whose
|
|
63
|
+
`headers` is a `Headers` instance and cannot be indexed (`TS7052`). `1.0.4`
|
|
64
|
+
introduced `LoggableRequest` and `LoggableResponse` for exactly this and exports
|
|
65
|
+
them; the README never adopted them. It does now, and narrows the
|
|
66
|
+
`string | string[]` a repeated header produces.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`pnpm check:published`** — verifies that the README's links resolve, that its
|
|
71
|
+
TypeScript snippets and the type tests compile against the built package, and
|
|
72
|
+
that every `v*.*.*` tag has a `## [x.y.z]` CHANGELOG section. It resolves the
|
|
73
|
+
package through its `exports` map into `dist/`, which `test:types` cannot do:
|
|
74
|
+
that one maps the package to `./src` through tsconfig `paths` and so never
|
|
75
|
+
compiles what the README claims. Both defects above were found by it.
|
|
76
|
+
|
|
77
|
+
Runs in CI, in `release.yml`, and inside `prepublishOnly`.
|
|
78
|
+
|
|
14
79
|
## [1.0.4] - 2026-07-29
|
|
15
80
|
|
|
16
81
|
### Fixed
|
|
@@ -187,7 +252,8 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
187
252
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
188
253
|
`release.yml`, Dependabot, and issue templates
|
|
189
254
|
|
|
190
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.
|
|
255
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...HEAD
|
|
256
|
+
[1.0.5]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...v1.0.5
|
|
191
257
|
[1.0.4]: https://github.com/bymaxone/nest-logger/compare/v1.0.3...v1.0.4
|
|
192
258
|
[1.0.3]: https://github.com/bymaxone/nest-logger/compare/v1.0.2...v1.0.3
|
|
193
259
|
[1.0.2]: https://github.com/bymaxone/nest-logger/compare/v1.0.1...v1.0.2
|
package/README.md
CHANGED
|
@@ -337,15 +337,21 @@ Once active, every log entry automatically carries `traceId`, `spanId`, and `tra
|
|
|
337
337
|
```typescript
|
|
338
338
|
// request-id.middleware.ts
|
|
339
339
|
import { Injectable, NestMiddleware } from '@nestjs/common'
|
|
340
|
-
import {
|
|
340
|
+
import {
|
|
341
|
+
LogContextService,
|
|
342
|
+
type LoggableRequest,
|
|
343
|
+
type LoggableResponse
|
|
344
|
+
} from '@bymax-one/nest-logger'
|
|
341
345
|
import { randomUUID } from 'node:crypto'
|
|
342
346
|
|
|
343
347
|
@Injectable()
|
|
344
348
|
export class RequestIdMiddleware implements NestMiddleware {
|
|
345
349
|
constructor(private readonly logContext: LogContextService) {}
|
|
346
350
|
|
|
347
|
-
use(req:
|
|
348
|
-
|
|
351
|
+
use(req: LoggableRequest, res: LoggableResponse, next: () => void) {
|
|
352
|
+
// A header can arrive repeated, so its type is `string | string[]`.
|
|
353
|
+
const raw = req.headers['x-request-id']
|
|
354
|
+
const requestId = (Array.isArray(raw) ? raw[0] : raw) ?? `r_${randomUUID()}`
|
|
349
355
|
this.logContext.run({ requestId }, next)
|
|
350
356
|
}
|
|
351
357
|
}
|
|
@@ -439,7 +445,8 @@ export class LokiDestination implements ILogDestination {
|
|
|
439
445
|
private readonly url: string
|
|
440
446
|
private readonly headers: Record<string, string>
|
|
441
447
|
private readonly labels: Record<string, string>
|
|
442
|
-
|
|
448
|
+
// Serialized lines, not objects — `write` receives the payload already encoded.
|
|
449
|
+
private buffer: string[] = []
|
|
443
450
|
private flushTimer?: NodeJS.Timeout
|
|
444
451
|
|
|
445
452
|
constructor(opts: {
|
|
@@ -466,8 +473,10 @@ export class LokiDestination implements ILogDestination {
|
|
|
466
473
|
await this.flush()
|
|
467
474
|
}
|
|
468
475
|
|
|
469
|
-
|
|
470
|
-
|
|
476
|
+
// `payload` already IS the serialized entry, newline-terminated. Buffer it as
|
|
477
|
+
// it arrives; parse only where a field is genuinely needed, as `flush` does.
|
|
478
|
+
write(payload: string): void {
|
|
479
|
+
this.buffer.push(payload)
|
|
471
480
|
if (this.buffer.length >= 100) void this.flush()
|
|
472
481
|
}
|
|
473
482
|
|
|
@@ -478,7 +487,10 @@ export class LokiDestination implements ILogDestination {
|
|
|
478
487
|
streams: [
|
|
479
488
|
{
|
|
480
489
|
stream: this.labels,
|
|
481
|
-
values: batch.map((
|
|
490
|
+
values: batch.map((line) => [
|
|
491
|
+
String(BigInt((JSON.parse(line) as LogEntry).time) * 1_000_000n),
|
|
492
|
+
line.trimEnd()
|
|
493
|
+
])
|
|
482
494
|
}
|
|
483
495
|
]
|
|
484
496
|
})
|
|
@@ -520,7 +532,10 @@ export class PrismaLogDestination implements ILogDestination {
|
|
|
520
532
|
|
|
521
533
|
constructor(private readonly prisma: PrismaClient) {}
|
|
522
534
|
|
|
523
|
-
write(
|
|
535
|
+
write(payload: string): void {
|
|
536
|
+
// This destination stores individual columns, so it is one of the few that
|
|
537
|
+
// has to parse. A sink that forwards the line verbatim should not.
|
|
538
|
+
const entry = JSON.parse(payload) as LogEntry
|
|
524
539
|
void this.prisma.applicationLog.create({
|
|
525
540
|
data: {
|
|
526
541
|
level: entry.level,
|
|
@@ -557,8 +572,9 @@ export class RollingFileDestination implements ILogDestination {
|
|
|
557
572
|
await new Promise<void>((resolve) => this.stream?.end(resolve))
|
|
558
573
|
}
|
|
559
574
|
|
|
560
|
-
write(
|
|
561
|
-
|
|
575
|
+
write(payload: string): void {
|
|
576
|
+
// Already newline-terminated JSON — re-serializing would double-encode it.
|
|
577
|
+
this.stream?.write(payload)
|
|
562
578
|
}
|
|
563
579
|
}
|
|
564
580
|
```
|
|
@@ -806,7 +822,8 @@ type LogEntry = {
|
|
|
806
822
|
interface ILogDestination {
|
|
807
823
|
readonly name: string
|
|
808
824
|
readonly minLevel?: LogLevel
|
|
809
|
-
|
|
825
|
+
/** `payload` is a newline-terminated JSON entry, UTF-8 encoded — not an object. */
|
|
826
|
+
write(payload: string): void | Promise<void>
|
|
810
827
|
onInit?(): Promise<void>
|
|
811
828
|
onShutdown?(): Promise<void>
|
|
812
829
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Structured JSON logging for NestJS based on Pino 10, with optional OpenTelemetry correlation.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@nestjs/common": "^11.0.
|
|
68
|
-
"@nestjs/core": "^11.
|
|
67
|
+
"@nestjs/common": "^11.0.16",
|
|
68
|
+
"@nestjs/core": "^11.1.18",
|
|
69
69
|
"@opentelemetry/api": "^1.9.0",
|
|
70
70
|
"pino": "^10.0.0",
|
|
71
71
|
"pino-pretty": "^13.0.0",
|
|
@@ -142,23 +142,24 @@
|
|
|
142
142
|
"registry": "https://registry.npmjs.org/"
|
|
143
143
|
},
|
|
144
144
|
"scripts": {
|
|
145
|
+
"bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
|
|
145
146
|
"build": "pnpm clean && tsup",
|
|
147
|
+
"check:exports": "attw --pack .",
|
|
148
|
+
"check:published": "node scripts/check-published-surface.mjs",
|
|
149
|
+
"clean": "rm -rf dist coverage",
|
|
146
150
|
"lint": "eslint src",
|
|
147
151
|
"lint:fix": "eslint src --fix",
|
|
148
|
-
"test": "jest",
|
|
149
|
-
"test:cov": "jest --coverage",
|
|
150
|
-
"test:watch": "jest --watch",
|
|
151
|
-
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
152
|
-
"test:all": "pnpm test && pnpm test:e2e",
|
|
153
|
-
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
154
152
|
"mutation": "stryker run",
|
|
155
|
-
"mutation:incremental": "stryker run --incremental",
|
|
156
153
|
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
157
|
-
"
|
|
158
|
-
"
|
|
154
|
+
"mutation:incremental": "stryker run --incremental",
|
|
155
|
+
"release": "pnpm publish --provenance",
|
|
159
156
|
"size": "node scripts/check-size.mjs",
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
157
|
+
"test": "jest",
|
|
158
|
+
"test:all": "pnpm test && pnpm test:e2e",
|
|
159
|
+
"test:cov": "jest --coverage",
|
|
160
|
+
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
161
|
+
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
162
|
+
"test:watch": "jest --watch",
|
|
163
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
|
|
163
164
|
}
|
|
164
165
|
}
|