@bymax-one/nest-logger 1.0.4 → 1.0.6
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 +86 -1
- package/README.md +33 -12
- package/package.json +28 -24
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,89 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.0.6] - 2026-08-06
|
|
15
|
+
|
|
16
|
+
**Tests and documentation only.** `dist/` is byte-identical to `1.0.5`.
|
|
17
|
+
|
|
18
|
+
An earlier revision of this branch added inline `// Stryker disable` comments and reported a
|
|
19
|
+
99.73% score off the back of them. That is against this package's own plan, which forbids them
|
|
20
|
+
because they ship in the unminified bundle and eat the server subpath's brotli budget. They are
|
|
21
|
+
gone; the score is the measured 97.42% with the equivalents documented instead.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- `detectOtelTraceApi` resolves `@opentelemetry/api`, and the specifier was pinned by nothing —
|
|
26
|
+
the spec mocks `createRequire`, so the resolver ignored what it was handed. Renaming the module
|
|
27
|
+
would have kept the suite green while every deployment silently lost trace correlation, because
|
|
28
|
+
a failed resolve is swallowed by design.
|
|
29
|
+
- `useNestLogger` gained a feature-module case, the shape where the provider sits outside the host
|
|
30
|
+
module's own injector.
|
|
31
|
+
|
|
32
|
+
## [1.0.5] - 2026-07-30
|
|
33
|
+
|
|
34
|
+
### Security
|
|
35
|
+
|
|
36
|
+
- **Peer floors raised to exclude known-vulnerable NestJS versions.** `@nestjs/common ^11.0.0`
|
|
37
|
+
admitted 11.0.0–11.0.15, carrying [GHSA-cj7v-w2c7-cp7c](https://github.com/advisories/GHSA-cj7v-w2c7-cp7c)
|
|
38
|
+
(remote code execution via the `Content-Type` header, patched in 11.0.16), and
|
|
39
|
+
`@nestjs/core ^11.0.0` admitted everything up to 11.1.17, carrying
|
|
40
|
+
[GHSA-36xv-jgw5-4q75](https://github.com/advisories/GHSA-36xv-jgw5-4q75) (patched in 11.1.18).
|
|
41
|
+
|
|
42
|
+
A peer range states which versions this library supports. A floor below a published
|
|
43
|
+
advisory told a consumer a vulnerable install was supported, and nothing in their
|
|
44
|
+
tooling contradicted it — the install resolved cleanly and silently. Floors are now
|
|
45
|
+
`^11.0.16` and `^11.1.18`.
|
|
46
|
+
|
|
47
|
+
Shipped as a patch, which is where a security fix belongs; a minor would reach the
|
|
48
|
+
same installs anyway, since `^1.0.4` accepts `1.1.0` as readily as `1.0.5`. No
|
|
49
|
+
runtime behaviour changed.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **The custom-destination examples did not compile against `ILogDestination`.**
|
|
54
|
+
The published interface takes a serialized line:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
write(payload: string): void | Promise<void> // newline-terminated JSON, UTF-8
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
All three examples in the README — `LokiDestination`, `PrismaLogDestination`,
|
|
61
|
+
`RollingFileDestination` — and the API reference table declared
|
|
62
|
+
`write(entry: LogEntry): void`, so anyone following the README to build a
|
|
63
|
+
destination got:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
TS2416: Property 'write' in type 'LokiDestination' is not assignable to the
|
|
67
|
+
same property in base type 'ILogDestination'.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`ILogDestination` is this library's extension point, so the documentation was
|
|
71
|
+
teaching the wrong shape for the one thing consumers are meant to implement.
|
|
72
|
+
`RollingFileDestination` compounded it by re-serializing a payload that already
|
|
73
|
+
**is** newline-terminated JSON — anyone casting past the type error would have
|
|
74
|
+
written double-encoded lines to disk.
|
|
75
|
+
|
|
76
|
+
Each example now does what its own job requires rather than all three doing the
|
|
77
|
+
same thing: Loki buffers the line verbatim, Prisma parses because it stores
|
|
78
|
+
individual columns, rolling-file writes it through untouched.
|
|
79
|
+
|
|
80
|
+
- **The request-id middleware typed `req` as the global DOM `Request`**, whose
|
|
81
|
+
`headers` is a `Headers` instance and cannot be indexed (`TS7052`). `1.0.4`
|
|
82
|
+
introduced `LoggableRequest` and `LoggableResponse` for exactly this and exports
|
|
83
|
+
them; the README never adopted them. It does now, and narrows the
|
|
84
|
+
`string | string[]` a repeated header produces.
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
|
|
88
|
+
- **`pnpm check:published`** — verifies that the README's links resolve, that its
|
|
89
|
+
TypeScript snippets and the type tests compile against the built package, and
|
|
90
|
+
that every `v*.*.*` tag has a `## [x.y.z]` CHANGELOG section. It resolves the
|
|
91
|
+
package through its `exports` map into `dist/`, which `test:types` cannot do:
|
|
92
|
+
that one maps the package to `./src` through tsconfig `paths` and so never
|
|
93
|
+
compiles what the README claims. Both defects above were found by it.
|
|
94
|
+
|
|
95
|
+
Runs in CI, in `release.yml`, and inside `prepublishOnly`.
|
|
96
|
+
|
|
14
97
|
## [1.0.4] - 2026-07-29
|
|
15
98
|
|
|
16
99
|
### Fixed
|
|
@@ -187,7 +270,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
187
270
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
188
271
|
`release.yml`, Dependabot, and issue templates
|
|
189
272
|
|
|
190
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.
|
|
273
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...HEAD
|
|
274
|
+
[1.0.6]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...v1.0.6
|
|
275
|
+
[1.0.5]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...v1.0.5
|
|
191
276
|
[1.0.4]: https://github.com/bymaxone/nest-logger/compare/v1.0.3...v1.0.4
|
|
192
277
|
[1.0.3]: https://github.com/bymaxone/nest-logger/compare/v1.0.2...v1.0.3
|
|
193
278
|
[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
|
```
|
|
@@ -729,7 +745,11 @@ When integrating `@bymax-one/nest-logger` in production, verify each of the foll
|
|
|
729
745
|
A logger runs on every request of every service that installs it, so the suite is held to a bar beyond "it runs" — every behavior is pinned so that a regression **fails a test**.
|
|
730
746
|
|
|
731
747
|
- ✅ **100% line coverage** — statements, branches, functions, and lines, enforced as a release gate across unit + e2e
|
|
732
|
-
- ✅ **97.42% mutation score** — verified with [Stryker](https://stryker-mutator.io/) against a
|
|
748
|
+
- ✅ **97.42% mutation score** — verified with [Stryker](https://stryker-mutator.io/) against a
|
|
749
|
+
`break` threshold of 95. The ten survivors are documented equivalents rather than coverage gaps,
|
|
750
|
+
and they stay uncounted-against rather than silenced: this package's plan forbids inline
|
|
751
|
+
`// Stryker disable` comments because they ship in the unminified bundle (see the
|
|
752
|
+
[report](./docs/mutation_testing_results.md))
|
|
733
753
|
- ✅ **Every survivor documented** — the 10 remaining mutants are equivalents, each recorded in [docs/mutation_testing_results.md](./docs/mutation_testing_results.md) rather than silenced with an inline comment, so the score is an accounting rather than a number
|
|
734
754
|
- ✅ **No real I/O in unit tests** — the Pino instance and every destination are mocked; e2e tests exercise the wired module through `@nestjs/testing` and supertest
|
|
735
755
|
|
|
@@ -806,7 +826,8 @@ type LogEntry = {
|
|
|
806
826
|
interface ILogDestination {
|
|
807
827
|
readonly name: string
|
|
808
828
|
readonly minLevel?: LogLevel
|
|
809
|
-
|
|
829
|
+
/** `payload` is a newline-terminated JSON entry, UTF-8 encoded — not an object. */
|
|
830
|
+
write(payload: string): void | Promise<void>
|
|
810
831
|
onInit?(): Promise<void>
|
|
811
832
|
onShutdown?(): Promise<void>
|
|
812
833
|
}
|
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.6",
|
|
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",
|
|
@@ -53,6 +53,29 @@
|
|
|
53
53
|
]
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
|
|
58
|
+
"build": "pnpm clean && tsup",
|
|
59
|
+
"check:exports": "attw --pack .",
|
|
60
|
+
"check:published": "node scripts/check-published-surface.mjs",
|
|
61
|
+
"clean": "rm -rf dist coverage",
|
|
62
|
+
"lint": "eslint src",
|
|
63
|
+
"lint:fix": "eslint src --fix",
|
|
64
|
+
"mutation": "stryker run",
|
|
65
|
+
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
66
|
+
"mutation:incremental": "stryker run --incremental",
|
|
67
|
+
"prepare": "husky",
|
|
68
|
+
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published",
|
|
69
|
+
"release": "npm publish --provenance --access public",
|
|
70
|
+
"size": "node scripts/check-size.mjs",
|
|
71
|
+
"test": "jest",
|
|
72
|
+
"test:all": "pnpm test && pnpm test:e2e",
|
|
73
|
+
"test:cov": "jest --coverage",
|
|
74
|
+
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
75
|
+
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
76
|
+
"test:watch": "jest --watch",
|
|
77
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
|
|
78
|
+
},
|
|
56
79
|
"lint-staged": {
|
|
57
80
|
"*.{ts,tsx,js,mjs,cjs}": [
|
|
58
81
|
"eslint --fix",
|
|
@@ -64,8 +87,8 @@
|
|
|
64
87
|
},
|
|
65
88
|
"dependencies": {},
|
|
66
89
|
"peerDependencies": {
|
|
67
|
-
"@nestjs/common": "^11.0.
|
|
68
|
-
"@nestjs/core": "^11.
|
|
90
|
+
"@nestjs/common": "^11.0.16",
|
|
91
|
+
"@nestjs/core": "^11.1.18",
|
|
69
92
|
"@opentelemetry/api": "^1.9.0",
|
|
70
93
|
"pino": "^10.0.0",
|
|
71
94
|
"pino-pretty": "^13.0.0",
|
|
@@ -134,31 +157,12 @@
|
|
|
134
157
|
"request-id",
|
|
135
158
|
"pii-redaction"
|
|
136
159
|
],
|
|
160
|
+
"packageManager": "pnpm@11.20.0",
|
|
137
161
|
"engines": {
|
|
138
162
|
"node": ">=24.0.0"
|
|
139
163
|
},
|
|
140
164
|
"publishConfig": {
|
|
141
165
|
"access": "public",
|
|
142
166
|
"registry": "https://registry.npmjs.org/"
|
|
143
|
-
},
|
|
144
|
-
"scripts": {
|
|
145
|
-
"build": "pnpm clean && tsup",
|
|
146
|
-
"lint": "eslint src",
|
|
147
|
-
"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
|
-
"mutation": "stryker run",
|
|
155
|
-
"mutation:incremental": "stryker run --incremental",
|
|
156
|
-
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
157
|
-
"bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
|
|
158
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json",
|
|
159
|
-
"size": "node scripts/check-size.mjs",
|
|
160
|
-
"check:exports": "attw --pack .",
|
|
161
|
-
"clean": "rm -rf dist coverage",
|
|
162
|
-
"release": "pnpm publish --provenance"
|
|
163
167
|
}
|
|
164
|
-
}
|
|
168
|
+
}
|