@glassops/scribe 1.0.1 → 1.0.3
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 +90 -73
- package/dist/create-directory.d.ts +1 -1
- package/dist/create-directory.d.ts.map +1 -1
- package/dist/create-directory.js +12 -8
- package/dist/create-directory.js.map +1 -1
- package/dist/create-logger.d.ts +5 -4
- package/dist/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +104 -83
- package/dist/create-logger.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/types.d.ts +17 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities.d.ts +1 -9
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +49 -23
- package/dist/utilities.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,35 +20,42 @@ This ensures production, staging, and development never collide, and that multi
|
|
|
20
20
|
### Structured, operationally meaningful logs
|
|
21
21
|
|
|
22
22
|
- JSON‑structured entries with timestamps.
|
|
23
|
-
- Daily
|
|
24
|
-
- Semantic log levels (critical
|
|
25
|
-
- Request
|
|
23
|
+
- Daily-rotated files with configurable retention (default: 14 days).
|
|
24
|
+
- Semantic log levels (`critical`, `change`, `http`, etc.) mapped to dedicated transports.
|
|
25
|
+
- Request-scoped metadata (`tenant`, `service`, `environment`, `user`, `correlationId`, `requestId`).
|
|
26
26
|
|
|
27
27
|
### Deep, case‑insensitive redaction
|
|
28
28
|
|
|
29
29
|
Sensitive keys are removed at any depth, regardless of casing:
|
|
30
30
|
|
|
31
|
-
- password
|
|
32
|
-
- authorization
|
|
31
|
+
- `password`, `Password`, `PASSWORD`
|
|
32
|
+
- `authorization`, `Authorization`, etc.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Other behaviors:
|
|
35
|
+
|
|
36
|
+
- Circular references → `"[Circular]"`
|
|
37
|
+
- Buffers → `"[Binary]"`
|
|
35
38
|
|
|
36
39
|
### Transport‑agnostic architecture
|
|
37
40
|
|
|
38
41
|
File transports are enabled by default, but Scribe supports:
|
|
39
42
|
|
|
40
|
-
- Console‑only mode
|
|
41
|
-
- Cloud‑only mode
|
|
42
|
-
- Hybrid mode
|
|
43
|
+
- Console‑only mode
|
|
44
|
+
- Cloud‑only mode
|
|
45
|
+
- Hybrid mode
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
Configurable through:
|
|
45
48
|
|
|
46
49
|
- `disableFileTransports`
|
|
47
50
|
- `extraTransports`
|
|
48
51
|
|
|
49
52
|
### Pure, re‑entrant API
|
|
50
53
|
|
|
51
|
-
No global state
|
|
54
|
+
- No global state
|
|
55
|
+
- No shared mutable configuration
|
|
56
|
+
- No hidden defaults
|
|
57
|
+
|
|
58
|
+
Every logger instance is fully deterministic and scoped.
|
|
52
59
|
|
|
53
60
|
## Installation
|
|
54
61
|
|
|
@@ -60,18 +67,16 @@ npm install @glassops/scribe
|
|
|
60
67
|
|
|
61
68
|
Scribe is built around three principles:
|
|
62
69
|
|
|
63
|
-
- Identity is explicit
|
|
64
|
-
- Streams are semantic
|
|
65
|
-
- Safety is mandatory
|
|
70
|
+
- **Identity is explicit**: Every log entry carries a complete scope describing tenant, environment, service, and correlation identifiers.
|
|
71
|
+
- **Streams are semantic**: Incidents, changes, HTTP traffic, and application logs are treated as distinct operational categories.
|
|
72
|
+
- **Safety is mandatory**: Redaction, sanitization, and directory grammar are enforced before logs reach any transport.
|
|
66
73
|
|
|
67
74
|
## Directory structure
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
All segments of the directory path are sanitized to prevent traversal or invalid characters.
|
|
70
77
|
|
|
71
|
-
With service: `<target>/<service>/<tenant>`
|
|
72
|
-
Without service: `<target>/<tenant>/<environment>`
|
|
73
|
-
|
|
74
|
-
All segments are sanitized to prevent traversal or invalid characters.
|
|
78
|
+
- With service: `<target>/<service>/<tenant>`
|
|
79
|
+
- Without service: `<target>/<tenant>/<environment>`
|
|
75
80
|
|
|
76
81
|
## Usage
|
|
77
82
|
|
|
@@ -92,20 +97,24 @@ const directory = createDirectory("/var/logs", {
|
|
|
92
97
|
```ts
|
|
93
98
|
import { createLogger } from "@glassops/scribe";
|
|
94
99
|
|
|
95
|
-
const logger = createLogger(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
const logger = createLogger(
|
|
101
|
+
directory,
|
|
102
|
+
{
|
|
103
|
+
tenant: "acme",
|
|
104
|
+
environment: "production",
|
|
105
|
+
service: "billing",
|
|
106
|
+
correlationId: "abc123",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
level: "info",
|
|
110
|
+
requestLog: "requests-%DATE%.log",
|
|
111
|
+
appLog: "app-%DATE%.log",
|
|
112
|
+
incidentLog: "incident-%DATE%.log",
|
|
113
|
+
changeLog: "change-%DATE%.log",
|
|
114
|
+
logToConsole: true,
|
|
115
|
+
sensitiveKeys: ["password", "token"],
|
|
116
|
+
}
|
|
117
|
+
);
|
|
109
118
|
```
|
|
110
119
|
|
|
111
120
|
## Logging methods
|
|
@@ -124,47 +133,62 @@ Example:
|
|
|
124
133
|
```ts
|
|
125
134
|
logger.logInfo("User updated profile", {
|
|
126
135
|
userId: "123",
|
|
127
|
-
password: "secret", // redacted
|
|
136
|
+
password: "secret", // redacted automatically
|
|
128
137
|
});
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
## HTTP logging (Morgan integration)
|
|
132
141
|
|
|
142
|
+
Scribe supports direct integration with Morgan. ANSI sequences are stripped before writing to ensure clean JSON logs:
|
|
143
|
+
|
|
133
144
|
```ts
|
|
134
145
|
app.use(morgan("combined", { stream: logger.createLoggerStream() }));
|
|
135
146
|
```
|
|
136
147
|
|
|
137
|
-
ANSI sequences are stripped before writing, ensuring clean machine‑readable logs.
|
|
138
|
-
|
|
139
148
|
## Redaction behavior
|
|
140
149
|
|
|
141
|
-
|
|
150
|
+
- Case-insensitive key matching
|
|
151
|
+
- Nested sensitive fields are recursively redacted
|
|
152
|
+
- **Circular references**: `"[Circular]"`
|
|
153
|
+
- **Buffers**: `"[Binary]"`
|
|
154
|
+
|
|
155
|
+
## Scoping
|
|
156
|
+
|
|
157
|
+
You can derive a new logger with additional metadata:
|
|
142
158
|
|
|
143
159
|
```ts
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
nested: {
|
|
147
|
-
token: "[REDACTED]"
|
|
148
|
-
}
|
|
149
|
-
}
|
|
160
|
+
const userLogger = await logger.scope({ user: "steven" });
|
|
161
|
+
userLogger.logInfo("User action");
|
|
150
162
|
```
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
- Circular references → "[Circular]"
|
|
154
|
-
- Buffers → "[Binary]"
|
|
164
|
+
Produces a new logger instance with merged scope. Logger directory may change if scope affects service or environment.
|
|
155
165
|
|
|
156
|
-
|
|
166
|
+
Existing transports are closed and rebuilt automatically if necessary.
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
## Logger API
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
interface ScribeLogger {
|
|
172
|
+
logError(error: string | Error, context?: Record<string, unknown>): void;
|
|
173
|
+
logWarn(message: string, context?: Record<string, unknown>): void;
|
|
174
|
+
logInfo(message: string, context?: Record<string, unknown>): void;
|
|
175
|
+
logDebug(message: string, context?: Record<string, unknown>): void;
|
|
176
|
+
logIncident(message: string, context?: Record<string, unknown>): void;
|
|
177
|
+
logChange(message: string, context?: Record<string, unknown>): void;
|
|
178
|
+
createLoggerStream(): { write: (message: string) => void };
|
|
179
|
+
scope(partial: Partial<LoggerScope>): Promise<void>;
|
|
180
|
+
close(): Promise<void>;
|
|
181
|
+
logger: Logger;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
161
184
|
|
|
162
|
-
|
|
185
|
+
- `createLoggerStream()`: Returns a Morgan-compatible writable stream.
|
|
186
|
+
- `scope()`: Returns a `Promise<void>`; merges partial metadata into current logger.
|
|
187
|
+
- `close()`: Gracefully shuts down transports.
|
|
188
|
+
- `logger`: Exposes underlying Winston Logger instance.
|
|
163
189
|
|
|
164
190
|
## Transport configuration
|
|
165
191
|
|
|
166
|
-
Scribe supports file‑based, console‑only, cloud‑only, and hybrid modes.
|
|
167
|
-
|
|
168
192
|
```ts
|
|
169
193
|
interface LoggerOptions {
|
|
170
194
|
level: LogLevel;
|
|
@@ -175,7 +199,6 @@ interface LoggerOptions {
|
|
|
175
199
|
logToConsole?: boolean;
|
|
176
200
|
sensitiveKeys?: string[];
|
|
177
201
|
retention?: string;
|
|
178
|
-
|
|
179
202
|
disableFileTransports?: boolean;
|
|
180
203
|
extraTransports?: Transport[];
|
|
181
204
|
}
|
|
@@ -191,11 +214,13 @@ interface LoggerOptions {
|
|
|
191
214
|
- `change`
|
|
192
215
|
- `debug`
|
|
193
216
|
|
|
217
|
+
Each level is treated as a separate operational stream rather than a severity threshold.
|
|
218
|
+
|
|
194
219
|
## Build and publish
|
|
195
220
|
|
|
196
|
-
Scribe uses a strict file whitelist and
|
|
221
|
+
Scribe uses a strict file whitelist and deterministic build pipeline.
|
|
197
222
|
|
|
198
|
-
|
|
223
|
+
### Scripts
|
|
199
224
|
|
|
200
225
|
```json
|
|
201
226
|
{
|
|
@@ -207,13 +232,13 @@ Scribe uses a strict file whitelist and a deterministic build pipeline.
|
|
|
207
232
|
}
|
|
208
233
|
```
|
|
209
234
|
|
|
210
|
-
Prepack ensures
|
|
235
|
+
Prepack ensures dist/ is always built before:
|
|
211
236
|
|
|
212
237
|
- `npm publish`
|
|
213
238
|
- `npm publish --dry-run`
|
|
214
239
|
- `npm pack`
|
|
215
240
|
|
|
216
|
-
Files included in the package
|
|
241
|
+
Files included in the package:
|
|
217
242
|
|
|
218
243
|
```json
|
|
219
244
|
{
|
|
@@ -221,29 +246,21 @@ Files included in the package
|
|
|
221
246
|
}
|
|
222
247
|
```
|
|
223
248
|
|
|
224
|
-
|
|
249
|
+
Guarantees only compiled output and documentation are published.
|
|
225
250
|
|
|
226
251
|
## Design rationale
|
|
227
252
|
|
|
228
|
-
- Directory grammar prevents cross
|
|
253
|
+
- Directory grammar prevents cross-environment and cross-tenant collisions.
|
|
229
254
|
- Semantic log levels allow operational tooling to treat incidents, changes, and HTTP traffic as distinct streams.
|
|
230
|
-
- Deep, case
|
|
231
|
-
- Pure, re
|
|
255
|
+
- Deep, case-insensitive redaction prevents credential leakage.
|
|
256
|
+
- Pure, re-entrant API ensures deterministic behavior across multi-tenant and multi-service deployments.
|
|
232
257
|
- Daily rotation keeps log files predictable for ingestion pipelines.
|
|
233
|
-
- Transport
|
|
258
|
+
- Transport-agnostic design supports file, console, cloud, and hybrid logging.
|
|
234
259
|
|
|
235
260
|
## License
|
|
236
261
|
|
|
237
262
|
Copyright [2026] [GlassOps Limited]
|
|
238
263
|
|
|
239
|
-
Licensed under the Apache License, Version 2.0 (
|
|
240
|
-
you may not use this file except in compliance with the License.
|
|
241
|
-
You may obtain a copy of the License at
|
|
242
|
-
|
|
243
|
-
<http://www.apache.org/licenses/LICENSE-2.0>
|
|
264
|
+
Licensed under the Apache License, Version 2.0 (["License"](LICENSE)).
|
|
244
265
|
|
|
245
|
-
Unless required by applicable law or agreed to in writing, software
|
|
246
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
247
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
248
|
-
See the License for the specific language governing permissions and
|
|
249
|
-
limitations under the License.
|
|
266
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|* provided LoggerScope. This is the only step containing
|
|
5
5
|
|* side-effects in Scribe's initialization flow.
|
|
6
6
|
\* -------------------------------------------------------------------------- */
|
|
7
|
-
import type { LoggerScope } from
|
|
7
|
+
import type { LoggerScope } from './types.js';
|
|
8
8
|
/**
|
|
9
9
|
* Resolves and creates the directory where log files will be written.
|
|
10
10
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-directory.d.ts","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA;;;;;gFAKgF;AAWhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,OAAO,WAAW,
|
|
1
|
+
{"version":3,"file":"create-directory.d.ts","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA;;;;;gFAKgF;AAWhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,OAAO,WAAW,WA2BjE,CAAC"}
|
package/dist/create-directory.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
/* --------------------------------------------------------------------------
|
|
8
8
|
|* NPM Dependencies
|
|
9
9
|
\* -------------------------------------------------------------------------- */
|
|
10
|
-
import fs from
|
|
11
|
-
import path from
|
|
12
|
-
import { safe } from
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { safe } from './utilities.js';
|
|
13
13
|
/* -------------------------------------------------------------------------- *\
|
|
14
14
|
|* Create Directory
|
|
15
15
|
\* -------------------------------------------------------------------------- */
|
|
@@ -25,14 +25,18 @@ import { safe } from "./utilities.js";
|
|
|
25
25
|
*/
|
|
26
26
|
export const createDirectory = (target, scope) => {
|
|
27
27
|
if (!target)
|
|
28
|
-
throw new Error(
|
|
28
|
+
throw new Error('Invalid log directory');
|
|
29
29
|
const { tenant, environment, service } = scope;
|
|
30
30
|
if (!tenant || !environment) {
|
|
31
|
-
throw new Error(
|
|
31
|
+
throw new Error('LoggerScope must include tenant and environment');
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const base = path.resolve(target);
|
|
34
|
+
const segmentA = service ? safe(service) : safe(tenant);
|
|
35
|
+
const segmentB = service ? safe(tenant) : safe(environment);
|
|
36
|
+
if (!segmentA || !segmentB) {
|
|
37
|
+
throw new Error('Invalid directory segment after sanitization');
|
|
38
|
+
}
|
|
39
|
+
const directory = path.join(base, segmentA, segmentB);
|
|
36
40
|
try {
|
|
37
41
|
fs.mkdirSync(directory, { recursive: true });
|
|
38
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-directory.js","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA;;;;;gFAKgF;AAEhF;;gFAEgF;AAChF,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAMxB,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC;;gFAEgF;AAChF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAE,KAAkB,EAAE,EAAE;IAClE,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAEtD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAE/C,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,
|
|
1
|
+
{"version":3,"file":"create-directory.js","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA;;;;;gFAKgF;AAEhF;;gFAEgF;AAChF,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAMxB,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC;;gFAEgF;AAChF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAE,KAAkB,EAAE,EAAE;IAClE,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAEtD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAE/C,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAE5D,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEtD,IAAI,CAAC;QACD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACL,MAAM,IAAI,KAAK,CAAC,mCAAmC,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC"}
|
package/dist/create-logger.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import winston from
|
|
2
|
-
import type { LoggerOptions, LoggerScope } from
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
import type { LoggerOptions, LoggerScope } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Creates a fully configured Winston logger with:
|
|
5
5
|
*
|
|
@@ -14,7 +14,7 @@ import type { LoggerOptions, LoggerScope } from "./types.js";
|
|
|
14
14
|
* `logError`, `logIncident`, etc.), a Morgan-compatible HTTP stream, and a
|
|
15
15
|
* `scope()` method for deriving new loggers with extended metadata.
|
|
16
16
|
*/
|
|
17
|
-
export declare const createLogger: (
|
|
17
|
+
export declare const createLogger: (targetDirectory: string, scope: LoggerScope, options: LoggerOptions) => {
|
|
18
18
|
logError: (error: string | Error, context?: Record<string, unknown>) => void;
|
|
19
19
|
logWarn: (message: string, context?: Record<string, unknown>) => void;
|
|
20
20
|
logInfo: (message: string, context?: Record<string, unknown>) => void;
|
|
@@ -24,7 +24,8 @@ export declare const createLogger: (directory: string, scope: LoggerScope, optio
|
|
|
24
24
|
createLoggerStream: () => {
|
|
25
25
|
write: (message: string) => void;
|
|
26
26
|
};
|
|
27
|
-
scope: (partial: Partial<LoggerScope>) =>
|
|
27
|
+
scope: (partial: Partial<LoggerScope>) => Promise<void>;
|
|
28
|
+
close: () => Promise<void>;
|
|
28
29
|
logger: winston.Logger;
|
|
29
30
|
};
|
|
30
31
|
//# sourceMappingURL=create-logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-logger.d.ts","sourceRoot":"","sources":["../src/create-logger.ts"],"names":[],"mappings":"AAUA,OAAO,OAAO,MAAM,SAAS,CAAC;AAO9B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"create-logger.d.ts","sourceRoot":"","sources":["../src/create-logger.ts"],"names":[],"mappings":"AAUA,OAAO,OAAO,MAAM,SAAS,CAAC;AAO9B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AAoCvE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GACrB,iBAAiB,MAAM,EACvB,OAAO,WAAW,EAClB,SAAS,aAAa;sBAwKA,MAAM,GAAG,KAAK,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;uBAG/C,MAAM,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;uBAGxC,MAAM,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;wBAGvC,MAAM,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;2BAGrC,MAAM,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;yBAG1C,MAAM,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;yBAIxC,MAAM;;qBA1CK,OAAO,CAAC,WAAW,CAAC;;;CAsD3D,CAAC"}
|
package/dist/create-logger.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import DailyRotateFile from
|
|
2
|
-
import winston from
|
|
1
|
+
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
2
|
+
import winston from 'winston';
|
|
3
3
|
const { combine, timestamp, errors, colorize, json } = winston.format;
|
|
4
|
-
import { redact, stripAnsi } from
|
|
4
|
+
import { redact, stripAnsi } from './utilities.js';
|
|
5
|
+
import { createDirectory } from './create-directory.js';
|
|
5
6
|
/* -------------------------------------------------------------------------- *\
|
|
6
7
|
|* Initialize Variables
|
|
7
8
|
\* -------------------------------------------------------------------------- */
|
|
@@ -21,13 +22,13 @@ const customLevels = Object.freeze({
|
|
|
21
22
|
debug: 6,
|
|
22
23
|
});
|
|
23
24
|
const customColors = Object.freeze({
|
|
24
|
-
critical:
|
|
25
|
-
error:
|
|
26
|
-
warn:
|
|
27
|
-
info:
|
|
28
|
-
http:
|
|
29
|
-
change:
|
|
30
|
-
debug:
|
|
25
|
+
critical: 'magenta',
|
|
26
|
+
error: 'red',
|
|
27
|
+
warn: 'yellow',
|
|
28
|
+
info: 'green',
|
|
29
|
+
http: 'blue',
|
|
30
|
+
change: 'cyan',
|
|
31
|
+
debug: 'gray',
|
|
31
32
|
});
|
|
32
33
|
winston.addColors(customColors);
|
|
33
34
|
/* -------------------------------------------------------------------------- *\
|
|
@@ -47,84 +48,84 @@ winston.addColors(customColors);
|
|
|
47
48
|
* `logError`, `logIncident`, etc.), a Morgan-compatible HTTP stream, and a
|
|
48
49
|
* `scope()` method for deriving new loggers with extended metadata.
|
|
49
50
|
*/
|
|
50
|
-
export const createLogger = (
|
|
51
|
-
|
|
51
|
+
export const createLogger = (targetDirectory, scope, options) => {
|
|
52
|
+
let currentScope = { ...scope };
|
|
53
|
+
let currentDirectory = createDirectory(targetDirectory, currentScope);
|
|
54
|
+
let closed = false;
|
|
52
55
|
const sensitiveKeys = [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
'password',
|
|
57
|
+
'token',
|
|
58
|
+
'secret',
|
|
59
|
+
'authorization',
|
|
57
60
|
...(options.sensitiveKeys ?? []),
|
|
58
61
|
];
|
|
59
|
-
const retention = options.retention ??
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
dirname: directory,
|
|
64
|
-
filename: options.requestLog,
|
|
65
|
-
datePattern: "YYYY-MM-DD",
|
|
66
|
-
level: "http",
|
|
67
|
-
zippedArchive: true,
|
|
68
|
-
maxFiles: retention,
|
|
69
|
-
format: combine(winston.format((log) => log.level === "http" ? log : false)(), timestamp(), json()),
|
|
70
|
-
}));
|
|
71
|
-
transports.push(new DailyRotateFile({
|
|
72
|
-
dirname: directory,
|
|
73
|
-
filename: options.appLog,
|
|
74
|
-
datePattern: "YYYY-MM-DD",
|
|
75
|
-
level: options.level,
|
|
76
|
-
zippedArchive: true,
|
|
77
|
-
maxFiles: retention,
|
|
78
|
-
format: combine(errors({ stack: environment !== "production" }), timestamp(), json()),
|
|
79
|
-
}));
|
|
80
|
-
if (options.incidentLog) {
|
|
62
|
+
const retention = options.retention ?? '14d';
|
|
63
|
+
const buildTransports = (dir) => {
|
|
64
|
+
const transports = [];
|
|
65
|
+
if (!options.disableFileTransports) {
|
|
81
66
|
transports.push(new DailyRotateFile({
|
|
82
|
-
dirname:
|
|
83
|
-
filename: options.
|
|
84
|
-
datePattern:
|
|
85
|
-
level:
|
|
67
|
+
dirname: dir,
|
|
68
|
+
filename: options.requestLog,
|
|
69
|
+
datePattern: 'YYYY-MM-DD',
|
|
70
|
+
level: 'http',
|
|
86
71
|
zippedArchive: true,
|
|
87
72
|
maxFiles: retention,
|
|
88
|
-
format: combine(winston.format((log) => log.level ===
|
|
73
|
+
format: combine(winston.format((log) => (log.level === 'http' ? log : false))(), timestamp(), json()),
|
|
89
74
|
}));
|
|
90
|
-
}
|
|
91
|
-
if (options.changeLog) {
|
|
92
75
|
transports.push(new DailyRotateFile({
|
|
93
|
-
dirname:
|
|
94
|
-
filename: options.
|
|
95
|
-
datePattern:
|
|
96
|
-
level:
|
|
76
|
+
dirname: dir,
|
|
77
|
+
filename: options.appLog,
|
|
78
|
+
datePattern: 'YYYY-MM-DD',
|
|
79
|
+
level: options.level,
|
|
97
80
|
zippedArchive: true,
|
|
98
81
|
maxFiles: retention,
|
|
99
|
-
format: combine(
|
|
82
|
+
format: combine(errors({ stack: scope.environment !== 'production' }), timestamp(), json()),
|
|
83
|
+
}));
|
|
84
|
+
if (options.incidentLog) {
|
|
85
|
+
transports.push(new DailyRotateFile({
|
|
86
|
+
dirname: dir,
|
|
87
|
+
filename: options.incidentLog,
|
|
88
|
+
datePattern: 'YYYY-MM-DD',
|
|
89
|
+
level: 'critical',
|
|
90
|
+
zippedArchive: true,
|
|
91
|
+
maxFiles: retention,
|
|
92
|
+
format: combine(winston.format((log) => (log.level === 'critical' ? log : false))(), timestamp(), json()),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
if (options.changeLog) {
|
|
96
|
+
transports.push(new DailyRotateFile({
|
|
97
|
+
dirname: dir,
|
|
98
|
+
filename: options.changeLog,
|
|
99
|
+
datePattern: 'YYYY-MM-DD',
|
|
100
|
+
level: 'change',
|
|
101
|
+
zippedArchive: true,
|
|
102
|
+
maxFiles: retention,
|
|
103
|
+
format: combine(winston.format((log) => (log.level === 'change' ? log : false))(), timestamp(), json()),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (options.logToConsole) {
|
|
108
|
+
transports.push(new winston.transports.Console({
|
|
109
|
+
format: combine(colorize(), timestamp()),
|
|
100
110
|
}));
|
|
101
111
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (options.extraTransports?.length) {
|
|
109
|
-
transports.push(...options.extraTransports);
|
|
110
|
-
}
|
|
112
|
+
if (options.extraTransports?.length) {
|
|
113
|
+
transports.push(...options.extraTransports);
|
|
114
|
+
}
|
|
115
|
+
return transports;
|
|
116
|
+
};
|
|
111
117
|
const loggerInstance = winston.createLogger({
|
|
112
118
|
levels: customLevels,
|
|
113
119
|
level: options.level,
|
|
114
|
-
transports,
|
|
120
|
+
transports: buildTransports(currentDirectory),
|
|
115
121
|
defaultMeta: {
|
|
116
|
-
|
|
117
|
-
service,
|
|
118
|
-
environment,
|
|
119
|
-
user,
|
|
120
|
-
correlationId,
|
|
121
|
-
requestId,
|
|
122
|
+
...currentScope,
|
|
122
123
|
pid: process.pid,
|
|
123
124
|
},
|
|
124
125
|
});
|
|
125
126
|
const log = (level, message, context = {}) => {
|
|
126
127
|
const safeContext = redact(context, sensitiveKeys);
|
|
127
|
-
const includeStack = environment !==
|
|
128
|
+
const includeStack = currentScope.environment !== 'production';
|
|
128
129
|
loggerInstance.log(level, message instanceof Error
|
|
129
130
|
? {
|
|
130
131
|
message: message.message,
|
|
@@ -133,27 +134,47 @@ export const createLogger = (directory, scope, options) => {
|
|
|
133
134
|
}
|
|
134
135
|
: { message, ...safeContext });
|
|
135
136
|
};
|
|
137
|
+
const close = async () => {
|
|
138
|
+
if (closed)
|
|
139
|
+
return;
|
|
140
|
+
closed = true;
|
|
141
|
+
await Promise.all(loggerInstance.transports.map((t) => new Promise((resolve) => {
|
|
142
|
+
t.on('finish', resolve);
|
|
143
|
+
t.end();
|
|
144
|
+
})));
|
|
145
|
+
loggerInstance.clear();
|
|
146
|
+
};
|
|
147
|
+
const scopeRebind = async (partial) => {
|
|
148
|
+
const newScope = { ...currentScope, ...partial };
|
|
149
|
+
const newDirectory = createDirectory(targetDirectory, newScope);
|
|
150
|
+
if (newDirectory !== currentDirectory) {
|
|
151
|
+
await close();
|
|
152
|
+
closed = false;
|
|
153
|
+
const newTransports = buildTransports(newDirectory);
|
|
154
|
+
newTransports.forEach((t) => loggerInstance.add(t));
|
|
155
|
+
currentDirectory = newDirectory;
|
|
156
|
+
}
|
|
157
|
+
currentScope = newScope;
|
|
158
|
+
loggerInstance.defaultMeta = { ...currentScope, pid: process.pid };
|
|
159
|
+
};
|
|
160
|
+
/* ----------------------------------------------------------------------
|
|
161
|
+
|* Return Public API
|
|
162
|
+
---------------------------------------------------------------------- */
|
|
136
163
|
return {
|
|
137
|
-
logError: (error, context = {}) => log(
|
|
138
|
-
logWarn: (message, context = {}) => log(
|
|
139
|
-
logInfo: (message, context = {}) => log(
|
|
140
|
-
logDebug: (message, context = {}) => log(
|
|
141
|
-
logIncident: (message, context = {}) => log(
|
|
142
|
-
logChange: (message, context = {}) => log(
|
|
164
|
+
logError: (error, context = {}) => log('error', error, context),
|
|
165
|
+
logWarn: (message, context = {}) => log('warn', message, context),
|
|
166
|
+
logInfo: (message, context = {}) => log('info', message, context),
|
|
167
|
+
logDebug: (message, context = {}) => log('debug', message, context),
|
|
168
|
+
logIncident: (message, context = {}) => log('critical', message, { category: 'incident', ...context }),
|
|
169
|
+
logChange: (message, context = {}) => log('change', message, { category: 'change', ...context }),
|
|
143
170
|
createLoggerStream: () => ({
|
|
144
171
|
write: (message) => {
|
|
145
|
-
const clean = stripAnsi(message).replace(/\n$/,
|
|
146
|
-
log(
|
|
147
|
-
source: "morgan",
|
|
148
|
-
service,
|
|
149
|
-
tenant,
|
|
150
|
-
environment,
|
|
151
|
-
correlationId,
|
|
152
|
-
requestId,
|
|
153
|
-
});
|
|
172
|
+
const clean = stripAnsi(message).replace(/\n$/, '');
|
|
173
|
+
log('http', clean, { source: 'morgan', ...currentScope });
|
|
154
174
|
},
|
|
155
175
|
}),
|
|
156
|
-
scope:
|
|
176
|
+
scope: scopeRebind,
|
|
177
|
+
close,
|
|
157
178
|
logger: loggerInstance,
|
|
158
179
|
};
|
|
159
180
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-logger.js","sourceRoot":"","sources":["../src/create-logger.ts"],"names":[],"mappings":"AASA,OAAO,eAAe,MAAM,2BAA2B,CAAC;AACxD,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;AAMtE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-logger.js","sourceRoot":"","sources":["../src/create-logger.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** ------------------------------------------------------------------------
|
|
2
2
|
|* @package @glassops/scribe
|
|
3
|
-
|* @version 1.0.
|
|
3
|
+
|* @version 1.0.2
|
|
4
4
|
|* @see https://www.jane-io.com
|
|
5
5
|
|* @license Apache-2.0
|
|
6
6
|
|* -------------------------------------------------------------------------
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* Public authoring contracts for configuring logger scope and behavior.
|
|
15
15
|
*/
|
|
16
|
-
export type { LoggerScope, LoggerOptions, LogLevel } from
|
|
16
|
+
export type { LoggerScope, LoggerOptions, LogLevel } from './types.js';
|
|
17
17
|
/**
|
|
18
18
|
* Pure, deterministic entrypoints for initializing the log directory and
|
|
19
19
|
* constructing a scoped logger instance. These are the primary runtime APIs.
|
|
20
20
|
*/
|
|
21
|
-
export { createDirectory } from
|
|
22
|
-
export { createLogger } from
|
|
21
|
+
export { createDirectory } from './create-directory.js';
|
|
22
|
+
export { createLogger } from './create-logger.js';
|
|
23
23
|
/**
|
|
24
24
|
* Internal helpers exposed for advanced use cases. These functions are pure
|
|
25
25
|
* and side‑effect‑free, but not required for normal operation.
|
|
26
26
|
*/
|
|
27
|
-
export { redact, safe, stripAnsi } from
|
|
27
|
+
export { redact, safe, stripAnsi } from './utilities.js';
|
|
28
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** ------------------------------------------------------------------------
|
|
2
2
|
|* @package @glassops/scribe
|
|
3
|
-
|* @version 1.0.
|
|
3
|
+
|* @version 1.0.2
|
|
4
4
|
|* @see https://www.jane-io.com
|
|
5
5
|
|* @license Apache-2.0
|
|
6
6
|
|* -------------------------------------------------------------------------
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Pure, deterministic entrypoints for initializing the log directory and
|
|
18
18
|
* constructing a scoped logger instance. These are the primary runtime APIs.
|
|
19
19
|
*/
|
|
20
|
-
export { createDirectory } from
|
|
21
|
-
export { createLogger } from
|
|
20
|
+
export { createDirectory } from './create-directory.js';
|
|
21
|
+
export { createLogger } from './create-logger.js';
|
|
22
22
|
/* --------------------------------------------------------------------------
|
|
23
23
|
|* Utilities
|
|
24
24
|
\* -------------------------------------------------------------------------- */
|
|
@@ -26,5 +26,5 @@ export { createLogger } from "./create-logger.js";
|
|
|
26
26
|
* Internal helpers exposed for advanced use cases. These functions are pure
|
|
27
27
|
* and side‑effect‑free, but not required for normal operation.
|
|
28
28
|
*/
|
|
29
|
-
export { redact, safe, stripAnsi } from
|
|
29
|
+
export { redact, safe, stripAnsi } from './utilities.js';
|
|
30
30
|
//# sourceMappingURL=index.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,22 @@
|
|
|
6
6
|
/* --------------------------------------------------------------------------
|
|
7
7
|
|* NPM Dependencies
|
|
8
8
|
\* -------------------------------------------------------------------------- */
|
|
9
|
-
import type
|
|
9
|
+
import type { Logger } from 'winston';
|
|
10
|
+
import type Transport from 'winston-transport';
|
|
11
|
+
export interface ScribeLogger {
|
|
12
|
+
logError: (error: string | Error, context?: Record<string, unknown>) => void;
|
|
13
|
+
logWarn: (message: string, context?: Record<string, unknown>) => void;
|
|
14
|
+
logInfo: (message: string, context?: Record<string, unknown>) => void;
|
|
15
|
+
logDebug: (message: string, context?: Record<string, unknown>) => void;
|
|
16
|
+
logIncident: (message: string, context?: Record<string, unknown>) => void;
|
|
17
|
+
logChange: (message: string, context?: Record<string, unknown>) => void;
|
|
18
|
+
createLoggerStream: () => {
|
|
19
|
+
write: (message: string) => void;
|
|
20
|
+
};
|
|
21
|
+
scope: (partial: Partial<LoggerScope>) => Promise<void>;
|
|
22
|
+
close: () => Promise<void>;
|
|
23
|
+
logger: Logger;
|
|
24
|
+
}
|
|
10
25
|
/**
|
|
11
26
|
* Describes the identity of the log stream. Every log entry is tagged with
|
|
12
27
|
* these fields to ensure deterministic partitioning across tenants,
|
|
@@ -62,5 +77,5 @@ export interface LoggerOptions {
|
|
|
62
77
|
* Supported semantic log levels. These map directly to Scribe's custom
|
|
63
78
|
* level hierarchy and determine which transport receives each log entry.
|
|
64
79
|
*/
|
|
65
|
-
export type LogLevel =
|
|
80
|
+
export type LogLevel = 'critical' | 'error' | 'warn' | 'info' | 'http' | 'change' | 'debug';
|
|
66
81
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;gFAOgF;AAChF,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAK/C;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;IAC9B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC;AAKD;;;GAGG;AACH,MAAM,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;gFAOgF;AAChF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAK/C,MAAM,WAAW,YAAY;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7E,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACvE,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1E,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxE,kBAAkB,EAAE,MAAM;QAAE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAC/D,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAClB;AAKD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;IAC9B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC;AAKD;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC"}
|
package/dist/utilities.d.ts
CHANGED
|
@@ -23,13 +23,5 @@ export declare const safe: (value: string) => string;
|
|
|
23
23
|
* control characters.
|
|
24
24
|
*/
|
|
25
25
|
export declare const stripAnsi: (log: string) => string;
|
|
26
|
-
|
|
27
|
-
* Recursively removes sensitive fields from an input object. Supports nested
|
|
28
|
-
* objects, arrays, and circular references. Sensitive keys are replaced with
|
|
29
|
-
* "[REDACTED]". Buffers are replaced with "[Binary]" to avoid dumping raw
|
|
30
|
-
* binary data into logs. All other values are preserved as‑is.
|
|
31
|
-
*
|
|
32
|
-
* This function never mutates the input and guarantees deterministic output.
|
|
33
|
-
*/
|
|
34
|
-
export declare const redact: (input: unknown, sensitiveKeys: string[], seen?: WeakSet<object>) => unknown;
|
|
26
|
+
export declare const redact: (value: unknown, sensitiveKeys: string[], depth?: number, seen?: WeakSet<object>) => unknown;
|
|
35
27
|
//# sourceMappingURL=utilities.d.ts.map
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;gFAUgF;AAChF;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;gFAUgF;AAChF;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,WAajC,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,WAEY,CAAC;AAiBlD,eAAO,MAAM,MAAM,GACf,OAAO,OAAO,EACd,eAAe,MAAM,EAAE,EACvB,cAAS,EACT,sBAA4B,KAC7B,OA4CF,CAAC"}
|
package/dist/utilities.js
CHANGED
|
@@ -15,7 +15,18 @@
|
|
|
15
15
|
* deterministic, cross‑platform directory names and prevents path traversal
|
|
16
16
|
* or invalid characters from entering the log directory grammar.
|
|
17
17
|
*/
|
|
18
|
-
export const safe = (value) =>
|
|
18
|
+
export const safe = (value) => {
|
|
19
|
+
const result = value
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/-+/g, '-')
|
|
24
|
+
.replace(/^-|-$/g, '');
|
|
25
|
+
if (!result) {
|
|
26
|
+
throw new Error('Invalid directory segment');
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
19
30
|
/* -------------------------------------------------------------------------- *\
|
|
20
31
|
|* Strip ANSI
|
|
21
32
|
\* -------------------------------------------------------------------------- */
|
|
@@ -27,7 +38,7 @@ export const safe = (value) => value.toLowerCase().replace(/[^a-zA-Z0-9-_]/g, "-
|
|
|
27
38
|
*/
|
|
28
39
|
export const stripAnsi = (log) =>
|
|
29
40
|
// eslint-disable-next-line no-control-regex
|
|
30
|
-
log.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g,
|
|
41
|
+
log.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
31
42
|
/* -------------------------------------------------------------------------- *\
|
|
32
43
|
|* Redact
|
|
33
44
|
\* -------------------------------------------------------------------------- */
|
|
@@ -39,29 +50,44 @@ log.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
|
39
50
|
*
|
|
40
51
|
* This function never mutates the input and guarantees deterministic output.
|
|
41
52
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
const MAX_DEPTH = 6;
|
|
54
|
+
const MAX_KEYS = 100;
|
|
55
|
+
const MAX_STRING_LENGTH = 10_000;
|
|
56
|
+
export const redact = (value, sensitiveKeys, depth = 0, seen = new WeakSet()) => {
|
|
57
|
+
if (value === null || value === undefined) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === 'object') {
|
|
61
|
+
if (seen.has(value)) {
|
|
62
|
+
return '[Circular]';
|
|
63
|
+
}
|
|
64
|
+
seen.add(value);
|
|
65
|
+
}
|
|
66
|
+
if (depth > MAX_DEPTH) {
|
|
67
|
+
return '[MaxDepthExceeded]';
|
|
45
68
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
if (typeof value === 'string') {
|
|
70
|
+
return value.length > MAX_STRING_LENGTH
|
|
71
|
+
? value.slice(0, MAX_STRING_LENGTH) + '...[Truncated]'
|
|
72
|
+
: value;
|
|
73
|
+
}
|
|
74
|
+
if (typeof value !== 'object') {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
return value.slice(0, MAX_KEYS).map((item) => redact(item, sensitiveKeys, depth + 1, seen));
|
|
79
|
+
}
|
|
80
|
+
const output = {};
|
|
81
|
+
const entries = Object.entries(value).slice(0, MAX_KEYS);
|
|
82
|
+
const normalizedKeys = new Set(sensitiveKeys.map((k) => k.toLowerCase()));
|
|
83
|
+
for (const [key, val] of entries) {
|
|
84
|
+
if (normalizedKeys.has(key.toLowerCase())) {
|
|
85
|
+
output[key] = '[REDACTED]';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
output[key] = redact(val, sensitiveKeys, depth + 1, seen);
|
|
62
89
|
}
|
|
63
|
-
return clone;
|
|
64
90
|
}
|
|
65
|
-
return
|
|
91
|
+
return output;
|
|
66
92
|
};
|
|
67
93
|
//# sourceMappingURL=utilities.js.map
|
package/dist/utilities.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;gFAUgF;AAChF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|