@jaypie/logger 1.2.17 → 1.2.19
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/dist/cjs/index.cjs +302 -7
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +142 -0
- package/dist/cjs/{JaypieLogger.d.ts → src/JaypieLogger.d.ts} +10 -2
- package/dist/cjs/{Logger.d.ts → src/Logger.d.ts} +9 -2
- package/dist/cjs/{constants.d.ts → src/constants.d.ts} +6 -0
- package/dist/cjs/{index.d.ts → src/index.d.ts} +1 -0
- package/dist/cjs/src/limits.d.ts +55 -0
- package/dist/esm/index.d.ts +142 -8
- package/dist/esm/index.js +302 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/{JaypieLogger.d.ts → src/JaypieLogger.d.ts} +10 -2
- package/dist/esm/{Logger.d.ts → src/Logger.d.ts} +9 -2
- package/dist/esm/src/__tests__/limits.spec.d.ts +1 -0
- package/dist/esm/src/__tests__/sanitizeAuth.spec.d.ts +1 -0
- package/dist/esm/{constants.d.ts → src/constants.d.ts} +6 -0
- package/dist/esm/src/index.d.ts +9 -0
- package/dist/esm/src/limits.d.ts +55 -0
- package/package.json +10 -4
- /package/dist/cjs/{__tests__ → src/__tests__}/datadogTransport.spec.d.ts +0 -0
- /package/dist/cjs/{__tests__ → src/__tests__}/index.spec.d.ts +0 -0
- /package/dist/cjs/{__tests__/sanitizeAuth.spec.d.ts → src/__tests__/limits.spec.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/__tests__/sanitizeAuth.spec.d.ts +0 -0
- /package/dist/cjs/{datadogTransport.d.ts → src/datadogTransport.d.ts} +0 -0
- /package/dist/cjs/{forceVar.d.ts → src/forceVar.d.ts} +0 -0
- /package/dist/cjs/{logTags.d.ts → src/logTags.d.ts} +0 -0
- /package/dist/cjs/{logVar.d.ts → src/logVar.d.ts} +0 -0
- /package/dist/cjs/{pipelines.d.ts → src/pipelines.d.ts} +0 -0
- /package/dist/cjs/{sanitizeAuth.d.ts → src/sanitizeAuth.d.ts} +0 -0
- /package/dist/cjs/{utils.d.ts → src/utils.d.ts} +0 -0
- /package/dist/esm/{__tests__ → src/__tests__}/datadogTransport.spec.d.ts +0 -0
- /package/dist/esm/{__tests__ → src/__tests__}/index.spec.d.ts +0 -0
- /package/dist/esm/{datadogTransport.d.ts → src/datadogTransport.d.ts} +0 -0
- /package/dist/esm/{forceVar.d.ts → src/forceVar.d.ts} +0 -0
- /package/dist/esm/{logTags.d.ts → src/logTags.d.ts} +0 -0
- /package/dist/esm/{logVar.d.ts → src/logVar.d.ts} +0 -0
- /package/dist/esm/{pipelines.d.ts → src/pipelines.d.ts} +0 -0
- /package/dist/esm/{sanitizeAuth.d.ts → src/sanitizeAuth.d.ts} +0 -0
- /package/dist/esm/{utils.d.ts → src/utils.d.ts} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-facing limit options. `false` explicitly disables a limit;
|
|
3
|
+
* `undefined` resolves from env vars, then defaults.
|
|
4
|
+
*/
|
|
5
|
+
export interface SerializationLimitOptions {
|
|
6
|
+
maxDepth?: number | false;
|
|
7
|
+
maxEntryBytes?: number | false;
|
|
8
|
+
maxStringLength?: number | false;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolved limits. `undefined` means the limit is off.
|
|
12
|
+
*/
|
|
13
|
+
export interface SerializationLimits {
|
|
14
|
+
maxDepth?: number;
|
|
15
|
+
maxEntryBytes?: number;
|
|
16
|
+
maxStringLength?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Characters preserved when an oversized entry truncates an attribute */
|
|
19
|
+
export declare const ENTRY_PREVIEW_LENGTH = 72;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve limits from explicit options, env vars, then defaults.
|
|
22
|
+
* `maxEntryBytes` defaults on (payloads must fit the log pipeline);
|
|
23
|
+
* `maxDepth` and `maxStringLength` default off.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveSerializationLimits(options?: SerializationLimitOptions): SerializationLimits;
|
|
26
|
+
export declare function hasValueLimits(limits: SerializationLimits): boolean;
|
|
27
|
+
export declare function byteLength(value: unknown): number;
|
|
28
|
+
/**
|
|
29
|
+
* Keep the first `maxLength` characters and append a visible marker
|
|
30
|
+
* preserving the dropped size
|
|
31
|
+
*/
|
|
32
|
+
export declare function truncateString(value: string, maxLength: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* Fit a string to a byte budget, reserving room for the marker and
|
|
35
|
+
* never cutting below the preview length
|
|
36
|
+
*/
|
|
37
|
+
export declare function truncateToBudget(value: string, budgetBytes: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* Walk a value applying maxStringLength and maxDepth. Returns a new value;
|
|
40
|
+
* never mutates the input. Only plain objects and arrays are traversed so
|
|
41
|
+
* class instances (Error, Date, ...) keep their serialization behavior.
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyValueLimits(value: unknown, limits: SerializationLimits, depth?: number, seen?: WeakSet<object>): unknown;
|
|
44
|
+
/**
|
|
45
|
+
* Fit a serialized log entry under maxEntryBytes. Truncates the top-level
|
|
46
|
+
* attributes of `data` largest-first to short previews until the entry fits,
|
|
47
|
+
* collapsing `data` to a byte-count marker only as a last resort. When
|
|
48
|
+
* `syncMessageToData` is set (var entries and single-object messages, where
|
|
49
|
+
* `message` mirrors `data`), the message is rebuilt from the truncated data.
|
|
50
|
+
* Returns a new entry; never mutates the input.
|
|
51
|
+
*/
|
|
52
|
+
export declare function enforceEntryLimit(entry: Record<string, unknown>, { maxEntryBytes, syncMessageToData, }: {
|
|
53
|
+
maxEntryBytes: number;
|
|
54
|
+
syncMessageToData?: boolean;
|
|
55
|
+
}): Record<string, unknown>;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,8 +1,142 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Caller-facing limit options. `false` explicitly disables a limit;
|
|
3
|
+
* `undefined` resolves from env vars, then defaults.
|
|
4
|
+
*/
|
|
5
|
+
interface SerializationLimitOptions {
|
|
6
|
+
maxDepth?: number | false;
|
|
7
|
+
maxEntryBytes?: number | false;
|
|
8
|
+
maxStringLength?: number | false;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolved limits. `undefined` means the limit is off.
|
|
12
|
+
*/
|
|
13
|
+
interface SerializationLimits {
|
|
14
|
+
maxDepth?: number;
|
|
15
|
+
maxEntryBytes?: number;
|
|
16
|
+
maxStringLength?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type LogLevel = string;
|
|
20
|
+
type LogFormat = "json" | "text";
|
|
21
|
+
type Tags = Record<string, string>;
|
|
22
|
+
interface LoggerOptions extends SerializationLimitOptions {
|
|
23
|
+
format?: LogFormat;
|
|
24
|
+
level?: LogLevel;
|
|
25
|
+
levelField?: boolean | string;
|
|
26
|
+
tags?: Tags;
|
|
27
|
+
varLevel?: LogLevel;
|
|
28
|
+
}
|
|
29
|
+
type LogMethod = {
|
|
30
|
+
(...messages: unknown[]): void;
|
|
31
|
+
var: (messageObject: unknown, messageValue?: unknown) => void;
|
|
32
|
+
};
|
|
33
|
+
declare class Logger {
|
|
34
|
+
debug: LogMethod;
|
|
35
|
+
error: LogMethod;
|
|
36
|
+
fatal: LogMethod;
|
|
37
|
+
info: LogMethod;
|
|
38
|
+
options: LoggerOptions;
|
|
39
|
+
tags: Tags;
|
|
40
|
+
trace: LogMethod;
|
|
41
|
+
var: (messageObject: unknown, messageValue?: unknown) => void;
|
|
42
|
+
warn: LogMethod;
|
|
43
|
+
private levelField;
|
|
44
|
+
private limits;
|
|
45
|
+
constructor({ format, level, levelField, maxDepth, maxEntryBytes, maxStringLength, tags, varLevel, }?: LoggerOptions);
|
|
46
|
+
private createLogMethod;
|
|
47
|
+
/**
|
|
48
|
+
* Update serialization limits at runtime. Pass a number to set a limit,
|
|
49
|
+
* `false` to disable one; omitted keys are unchanged.
|
|
50
|
+
*/
|
|
51
|
+
config(options?: SerializationLimitOptions): void;
|
|
52
|
+
tag(key: unknown, value?: unknown): void;
|
|
53
|
+
untag(key: unknown): void;
|
|
54
|
+
with(key: unknown, value?: unknown): Logger;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface JaypieLoggerOptions extends SerializationLimitOptions {
|
|
58
|
+
level?: string;
|
|
59
|
+
tags?: Record<string, string>;
|
|
60
|
+
}
|
|
61
|
+
declare class JaypieLogger {
|
|
62
|
+
debug: Logger["debug"];
|
|
63
|
+
error: Logger["error"];
|
|
64
|
+
fatal: Logger["fatal"];
|
|
65
|
+
info: Logger["info"];
|
|
66
|
+
level: string;
|
|
67
|
+
trace: Logger["trace"];
|
|
68
|
+
var: Logger["var"];
|
|
69
|
+
warn: Logger["warn"];
|
|
70
|
+
private _errorCount;
|
|
71
|
+
private _logger;
|
|
72
|
+
private _loggers;
|
|
73
|
+
private _params;
|
|
74
|
+
private _report;
|
|
75
|
+
private _sessionActive;
|
|
76
|
+
private _tags;
|
|
77
|
+
private _warnCount;
|
|
78
|
+
private _withLoggers;
|
|
79
|
+
constructor({ level, maxDepth, maxEntryBytes, maxStringLength, tags, }?: JaypieLoggerOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Update serialization limits at runtime for this logger and all loggers
|
|
82
|
+
* derived from it (lib, with, flag). Pass a number to set a limit,
|
|
83
|
+
* `false` to disable one; omitted keys are unchanged. Persists across
|
|
84
|
+
* init().
|
|
85
|
+
*/
|
|
86
|
+
config(options?: SerializationLimitOptions): void;
|
|
87
|
+
flag(flag?: string): JaypieLogger;
|
|
88
|
+
init(): void;
|
|
89
|
+
lib({ level, lib, tags, }?: {
|
|
90
|
+
level?: string;
|
|
91
|
+
lib?: string;
|
|
92
|
+
tags?: Record<string, string>;
|
|
93
|
+
}): JaypieLogger;
|
|
94
|
+
report(data: Record<string, unknown>): void;
|
|
95
|
+
setup(tags?: Record<string, unknown>): void;
|
|
96
|
+
tag(tags: Record<string, unknown>): void;
|
|
97
|
+
teardown(): void;
|
|
98
|
+
untag(key: unknown): void;
|
|
99
|
+
with(key: unknown, value?: unknown): JaypieLogger;
|
|
100
|
+
}
|
|
101
|
+
declare function createLogger(tags?: Record<string, string>): JaypieLogger;
|
|
102
|
+
|
|
103
|
+
declare const FORMAT: {
|
|
104
|
+
readonly JSON: "json";
|
|
105
|
+
readonly TEXT: "text";
|
|
106
|
+
};
|
|
107
|
+
declare const LEVEL: {
|
|
108
|
+
readonly ALL: "all";
|
|
109
|
+
readonly DEBUG: "debug";
|
|
110
|
+
readonly ERROR: "error";
|
|
111
|
+
readonly FATAL: "fatal";
|
|
112
|
+
readonly INFO: "info";
|
|
113
|
+
readonly SILENT: "silent";
|
|
114
|
+
readonly TRACE: "trace";
|
|
115
|
+
readonly WARN: "warn";
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare function isDatadogForwardingEnabled(): boolean;
|
|
119
|
+
declare class DatadogLogTransport {
|
|
120
|
+
private _beforeExitHandler;
|
|
121
|
+
private _buffer;
|
|
122
|
+
private _flushTimer;
|
|
123
|
+
private _ddsource;
|
|
124
|
+
private _env;
|
|
125
|
+
private _hostname;
|
|
126
|
+
private _service;
|
|
127
|
+
private _site;
|
|
128
|
+
constructor();
|
|
129
|
+
send(line: string, level: string): void;
|
|
130
|
+
flush(): void;
|
|
131
|
+
destroy(): void;
|
|
132
|
+
}
|
|
133
|
+
declare function getDatadogTransport(): DatadogLogTransport | null;
|
|
134
|
+
declare function _resetDatadogTransport(): void;
|
|
135
|
+
|
|
136
|
+
declare function redactAuth(value: unknown): string;
|
|
137
|
+
declare function sanitizeAuth(value: unknown): unknown;
|
|
138
|
+
|
|
139
|
+
declare const log: JaypieLogger;
|
|
140
|
+
|
|
141
|
+
export { FORMAT, LEVEL, Logger, _resetDatadogTransport, createLogger, log as default, getDatadogTransport, isDatadogForwardingEnabled, log, redactAuth, sanitizeAuth };
|
|
142
|
+
export type { SerializationLimitOptions, SerializationLimits };
|
package/dist/esm/index.js
CHANGED
|
@@ -4,8 +4,16 @@ import { request } from 'node:https';
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT = {
|
|
6
6
|
LEVEL: "debug",
|
|
7
|
+
// CloudWatch Logs caps events at 256KB and fronts Datadog in Lambda;
|
|
8
|
+
// Datadog's own per-log cap is 1MB. Truncate deliberately below both.
|
|
9
|
+
MAX_ENTRY_BYTES: 262144,
|
|
7
10
|
VAR_LEVEL: "debug",
|
|
8
11
|
};
|
|
12
|
+
const LIMIT_ENV = {
|
|
13
|
+
MAX_DEPTH: "LOG_MAX_DEPTH",
|
|
14
|
+
MAX_ENTRY_BYTES: "LOG_MAX_ENTRY_BYTES",
|
|
15
|
+
MAX_STRING: "LOG_MAX_STRING",
|
|
16
|
+
};
|
|
9
17
|
const ERROR_PREFIX = "[logger]";
|
|
10
18
|
const ERROR = {
|
|
11
19
|
VAR: {
|
|
@@ -59,6 +67,207 @@ const DATADOG_TRANSPORT = {
|
|
|
59
67
|
MAX_BATCH_SIZE: 100,
|
|
60
68
|
};
|
|
61
69
|
|
|
70
|
+
//
|
|
71
|
+
//
|
|
72
|
+
// Constants
|
|
73
|
+
//
|
|
74
|
+
const CIRCULAR_PLACEHOLDER = "[Circular]";
|
|
75
|
+
const DISABLED_ENV_VALUES = ["", "0", "false", "none", "off"];
|
|
76
|
+
const ELLIPSIS = "…";
|
|
77
|
+
// Reserve headroom for the truncation marker when fitting a string to a
|
|
78
|
+
// byte budget
|
|
79
|
+
const MARKER_RESERVE_BYTES = 64;
|
|
80
|
+
/** Characters preserved when an oversized entry truncates an attribute */
|
|
81
|
+
const ENTRY_PREVIEW_LENGTH = 72;
|
|
82
|
+
//
|
|
83
|
+
//
|
|
84
|
+
// Helpers
|
|
85
|
+
//
|
|
86
|
+
function isPlainObject(value) {
|
|
87
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const proto = Object.getPrototypeOf(value);
|
|
91
|
+
return proto === Object.prototype || proto === null;
|
|
92
|
+
}
|
|
93
|
+
function normalizeLimit(option) {
|
|
94
|
+
if (option === false)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (typeof option === "number" && Number.isFinite(option) && option > 0) {
|
|
97
|
+
return Math.floor(option);
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
function resolveLimit(option, envKey, defaultValue) {
|
|
102
|
+
if (option !== undefined) {
|
|
103
|
+
return normalizeLimit(option);
|
|
104
|
+
}
|
|
105
|
+
const raw = process.env[envKey];
|
|
106
|
+
if (raw !== undefined) {
|
|
107
|
+
if (DISABLED_ENV_VALUES.includes(raw.toLowerCase()))
|
|
108
|
+
return undefined;
|
|
109
|
+
const parsed = Number.parseInt(raw, 10);
|
|
110
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
return defaultValue;
|
|
114
|
+
}
|
|
115
|
+
function truncationMarker(droppedChars) {
|
|
116
|
+
return `${ELLIPSIS} [truncated ${droppedChars.toLocaleString("en-US")} chars]`;
|
|
117
|
+
}
|
|
118
|
+
//
|
|
119
|
+
//
|
|
120
|
+
// Main
|
|
121
|
+
//
|
|
122
|
+
/**
|
|
123
|
+
* Resolve limits from explicit options, env vars, then defaults.
|
|
124
|
+
* `maxEntryBytes` defaults on (payloads must fit the log pipeline);
|
|
125
|
+
* `maxDepth` and `maxStringLength` default off.
|
|
126
|
+
*/
|
|
127
|
+
function resolveSerializationLimits(options = {}) {
|
|
128
|
+
return {
|
|
129
|
+
maxDepth: resolveLimit(options.maxDepth, LIMIT_ENV.MAX_DEPTH),
|
|
130
|
+
maxEntryBytes: resolveLimit(options.maxEntryBytes, LIMIT_ENV.MAX_ENTRY_BYTES, DEFAULT.MAX_ENTRY_BYTES),
|
|
131
|
+
maxStringLength: resolveLimit(options.maxStringLength, LIMIT_ENV.MAX_STRING),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function hasValueLimits(limits) {
|
|
135
|
+
return limits.maxDepth !== undefined || limits.maxStringLength !== undefined;
|
|
136
|
+
}
|
|
137
|
+
function byteLength(value) {
|
|
138
|
+
try {
|
|
139
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
140
|
+
return Buffer.byteLength(str ?? "", "utf8");
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Keep the first `maxLength` characters and append a visible marker
|
|
148
|
+
* preserving the dropped size
|
|
149
|
+
*/
|
|
150
|
+
function truncateString(value, maxLength) {
|
|
151
|
+
if (value.length <= maxLength)
|
|
152
|
+
return value;
|
|
153
|
+
return value.slice(0, maxLength) + truncationMarker(value.length - maxLength);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Fit a string to a byte budget, reserving room for the marker and
|
|
157
|
+
* never cutting below the preview length
|
|
158
|
+
*/
|
|
159
|
+
function truncateToBudget(value, budgetBytes) {
|
|
160
|
+
if (Buffer.byteLength(value, "utf8") <= budgetBytes)
|
|
161
|
+
return value;
|
|
162
|
+
const maxLength = Math.max(ENTRY_PREVIEW_LENGTH, budgetBytes - MARKER_RESERVE_BYTES);
|
|
163
|
+
return truncateString(value, maxLength);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Walk a value applying maxStringLength and maxDepth. Returns a new value;
|
|
167
|
+
* never mutates the input. Only plain objects and arrays are traversed so
|
|
168
|
+
* class instances (Error, Date, ...) keep their serialization behavior.
|
|
169
|
+
*/
|
|
170
|
+
function applyValueLimits(value, limits, depth = 0, seen = new WeakSet()) {
|
|
171
|
+
const { maxDepth, maxStringLength } = limits;
|
|
172
|
+
if (typeof value === "string") {
|
|
173
|
+
return maxStringLength !== undefined
|
|
174
|
+
? truncateString(value, maxStringLength)
|
|
175
|
+
: value;
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
if (seen.has(value))
|
|
179
|
+
return CIRCULAR_PLACEHOLDER;
|
|
180
|
+
if (maxDepth !== undefined && depth > maxDepth) {
|
|
181
|
+
return `[Array(${value.length})]`;
|
|
182
|
+
}
|
|
183
|
+
seen.add(value);
|
|
184
|
+
const result = value.map((item) => applyValueLimits(item, limits, depth + 1, seen));
|
|
185
|
+
seen.delete(value);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
if (isPlainObject(value)) {
|
|
189
|
+
if (seen.has(value))
|
|
190
|
+
return CIRCULAR_PLACEHOLDER;
|
|
191
|
+
if (maxDepth !== undefined && depth > maxDepth) {
|
|
192
|
+
return "[Object]";
|
|
193
|
+
}
|
|
194
|
+
seen.add(value);
|
|
195
|
+
const result = {};
|
|
196
|
+
for (const key of Object.keys(value)) {
|
|
197
|
+
result[key] = applyValueLimits(value[key], limits, depth + 1, seen);
|
|
198
|
+
}
|
|
199
|
+
seen.delete(value);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
function truncateToPreview(value) {
|
|
205
|
+
const str = typeof value === "string"
|
|
206
|
+
? value
|
|
207
|
+
: (JSON.stringify(value) ?? String(value));
|
|
208
|
+
if (str.length <= ENTRY_PREVIEW_LENGTH)
|
|
209
|
+
return value;
|
|
210
|
+
return truncateString(str, ENTRY_PREVIEW_LENGTH);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Fit a serialized log entry under maxEntryBytes. Truncates the top-level
|
|
214
|
+
* attributes of `data` largest-first to short previews until the entry fits,
|
|
215
|
+
* collapsing `data` to a byte-count marker only as a last resort. When
|
|
216
|
+
* `syncMessageToData` is set (var entries and single-object messages, where
|
|
217
|
+
* `message` mirrors `data`), the message is rebuilt from the truncated data.
|
|
218
|
+
* Returns a new entry; never mutates the input.
|
|
219
|
+
*/
|
|
220
|
+
function enforceEntryLimit(entry, { maxEntryBytes, syncMessageToData = false, }) {
|
|
221
|
+
const originalBytes = byteLength(entry);
|
|
222
|
+
if (originalBytes <= maxEntryBytes)
|
|
223
|
+
return entry;
|
|
224
|
+
const result = { ...entry };
|
|
225
|
+
const sync = () => {
|
|
226
|
+
if (syncMessageToData) {
|
|
227
|
+
result.message =
|
|
228
|
+
typeof result.data === "string"
|
|
229
|
+
? result.data
|
|
230
|
+
: (JSON.stringify(result.data) ?? String(result.data));
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const data = result.data;
|
|
234
|
+
if (typeof data === "string") {
|
|
235
|
+
result.data = truncateToPreview(data);
|
|
236
|
+
sync();
|
|
237
|
+
}
|
|
238
|
+
else if (Array.isArray(data) || isPlainObject(data)) {
|
|
239
|
+
const container = Array.isArray(data)
|
|
240
|
+
? [...data]
|
|
241
|
+
: { ...data };
|
|
242
|
+
result.data = container;
|
|
243
|
+
const keys = Object.keys(container).sort((a, b) => byteLength(container[b]) - byteLength(container[a]));
|
|
244
|
+
for (const key of keys) {
|
|
245
|
+
container[key] = truncateToPreview(container[key]);
|
|
246
|
+
sync();
|
|
247
|
+
if (byteLength(result) <= maxEntryBytes)
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (typeof result.message === "string") {
|
|
252
|
+
// No structured data: the message itself is oversized
|
|
253
|
+
const overhead = originalBytes - byteLength(result.message);
|
|
254
|
+
result.message = truncateToBudget(result.message, Math.max(ENTRY_PREVIEW_LENGTH, maxEntryBytes - overhead));
|
|
255
|
+
}
|
|
256
|
+
if (byteLength(result) <= maxEntryBytes)
|
|
257
|
+
return result;
|
|
258
|
+
// Last resort: entry is still oversized after attribute-level truncation
|
|
259
|
+
const marker = `[truncated ${originalBytes.toLocaleString("en-US")} bytes]`;
|
|
260
|
+
if ("data" in result) {
|
|
261
|
+
result.data = marker;
|
|
262
|
+
if (syncMessageToData)
|
|
263
|
+
result.message = marker;
|
|
264
|
+
}
|
|
265
|
+
else if (typeof result.message === "string") {
|
|
266
|
+
result.message = marker;
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
62
271
|
//
|
|
63
272
|
// Key-based pipelines (match on var key name)
|
|
64
273
|
//
|
|
@@ -650,12 +859,22 @@ function resolveLevelField(value) {
|
|
|
650
859
|
return value;
|
|
651
860
|
}
|
|
652
861
|
class Logger {
|
|
653
|
-
constructor({ format = process.env.LOG_FORMAT || DEFAULT.LEVEL, level = process.env.LOG_LEVEL || DEFAULT.LEVEL, levelField, tags = {}, varLevel = process.env.LOG_VAR_LEVEL || DEFAULT.VAR_LEVEL, } = {}) {
|
|
862
|
+
constructor({ format = process.env.LOG_FORMAT || DEFAULT.LEVEL, level = process.env.LOG_LEVEL || DEFAULT.LEVEL, levelField, maxDepth, maxEntryBytes, maxStringLength, tags = {}, varLevel = process.env.LOG_VAR_LEVEL || DEFAULT.VAR_LEVEL, } = {}) {
|
|
654
863
|
this.levelField = resolveLevelField(levelField);
|
|
864
|
+
this.limits = resolveSerializationLimits({
|
|
865
|
+
maxDepth,
|
|
866
|
+
maxEntryBytes,
|
|
867
|
+
maxStringLength,
|
|
868
|
+
});
|
|
655
869
|
this.options = {
|
|
656
870
|
format,
|
|
657
871
|
level,
|
|
658
872
|
levelField: this.levelField || undefined,
|
|
873
|
+
// Pin resolved limits (false = explicitly off) so child loggers
|
|
874
|
+
// created via with() inherit this config instead of re-resolving
|
|
875
|
+
maxDepth: this.limits.maxDepth ?? false,
|
|
876
|
+
maxEntryBytes: this.limits.maxEntryBytes ?? false,
|
|
877
|
+
maxStringLength: this.limits.maxStringLength ?? false,
|
|
659
878
|
varLevel,
|
|
660
879
|
};
|
|
661
880
|
this.tags = {};
|
|
@@ -674,10 +893,16 @@ class Logger {
|
|
|
674
893
|
createLogMethod(logLevel, format, checkLevel) {
|
|
675
894
|
const logFn = (...messages) => {
|
|
676
895
|
if (LEVEL_VALUES[logLevel] <= LEVEL_VALUES[checkLevel]) {
|
|
677
|
-
|
|
896
|
+
let sanitized = messages.map(sanitizeAuth);
|
|
897
|
+
if (hasValueLimits(this.limits)) {
|
|
898
|
+
sanitized = sanitized.map((item) => applyValueLimits(item, this.limits));
|
|
899
|
+
}
|
|
678
900
|
if (format === FORMAT.JSON) {
|
|
679
901
|
let message = stringify(...sanitized);
|
|
680
902
|
let parses = parsesTo(message);
|
|
903
|
+
// When data comes from the full message they mirror each other;
|
|
904
|
+
// entry-limit truncation must keep them in sync
|
|
905
|
+
let syncMessageToData = parses.parses;
|
|
681
906
|
const last = sanitized[sanitized.length - 1];
|
|
682
907
|
if (sanitized.length > 1 &&
|
|
683
908
|
typeof last === "object" &&
|
|
@@ -689,6 +914,7 @@ class Logger {
|
|
|
689
914
|
if (lastParses.parses) {
|
|
690
915
|
message = stringify(...sanitized.slice(0, -1));
|
|
691
916
|
parses = lastParses;
|
|
917
|
+
syncMessageToData = false;
|
|
692
918
|
}
|
|
693
919
|
}
|
|
694
920
|
const json = {
|
|
@@ -701,10 +927,20 @@ class Logger {
|
|
|
701
927
|
if (this.levelField) {
|
|
702
928
|
json[this.levelField] = logLevel;
|
|
703
929
|
}
|
|
704
|
-
|
|
930
|
+
let entry = json;
|
|
931
|
+
if (this.limits.maxEntryBytes !== undefined) {
|
|
932
|
+
entry = enforceEntryLimit(json, {
|
|
933
|
+
maxEntryBytes: this.limits.maxEntryBytes,
|
|
934
|
+
syncMessageToData,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
out(entry, { level: logLevel });
|
|
705
938
|
}
|
|
706
939
|
else {
|
|
707
|
-
|
|
940
|
+
let message = stringify(...sanitized);
|
|
941
|
+
if (this.limits.maxEntryBytes !== undefined) {
|
|
942
|
+
message = truncateToBudget(message, this.limits.maxEntryBytes);
|
|
943
|
+
}
|
|
708
944
|
out(message, { level: logLevel });
|
|
709
945
|
}
|
|
710
946
|
}
|
|
@@ -746,6 +982,9 @@ class Logger {
|
|
|
746
982
|
}
|
|
747
983
|
}
|
|
748
984
|
messageVal = filterByType(messageVal);
|
|
985
|
+
if (hasValueLimits(this.limits)) {
|
|
986
|
+
messageVal = applyValueLimits(messageVal, this.limits);
|
|
987
|
+
}
|
|
749
988
|
const json = {
|
|
750
989
|
data: parse(messageVal),
|
|
751
990
|
dataType: typeof messageVal,
|
|
@@ -757,7 +996,14 @@ class Logger {
|
|
|
757
996
|
json[this.levelField] = logLevel;
|
|
758
997
|
}
|
|
759
998
|
if (LEVEL_VALUES[logLevel] <= LEVEL_VALUES[checkLevel]) {
|
|
760
|
-
|
|
999
|
+
let entry = json;
|
|
1000
|
+
if (this.limits.maxEntryBytes !== undefined) {
|
|
1001
|
+
entry = enforceEntryLimit(json, {
|
|
1002
|
+
maxEntryBytes: this.limits.maxEntryBytes,
|
|
1003
|
+
syncMessageToData: true,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
out(entry, { level: logLevel });
|
|
761
1007
|
}
|
|
762
1008
|
}
|
|
763
1009
|
else {
|
|
@@ -766,6 +1012,20 @@ class Logger {
|
|
|
766
1012
|
};
|
|
767
1013
|
return logFn;
|
|
768
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Update serialization limits at runtime. Pass a number to set a limit,
|
|
1017
|
+
* `false` to disable one; omitted keys are unchanged.
|
|
1018
|
+
*/
|
|
1019
|
+
config(options = {}) {
|
|
1020
|
+
this.limits = resolveSerializationLimits({
|
|
1021
|
+
maxDepth: options.maxDepth ?? this.limits.maxDepth ?? false,
|
|
1022
|
+
maxEntryBytes: options.maxEntryBytes ?? this.limits.maxEntryBytes ?? false,
|
|
1023
|
+
maxStringLength: options.maxStringLength ?? this.limits.maxStringLength ?? false,
|
|
1024
|
+
});
|
|
1025
|
+
this.options.maxDepth = this.limits.maxDepth ?? false;
|
|
1026
|
+
this.options.maxEntryBytes = this.limits.maxEntryBytes ?? false;
|
|
1027
|
+
this.options.maxStringLength = this.limits.maxStringLength ?? false;
|
|
1028
|
+
}
|
|
769
1029
|
tag(key, value) {
|
|
770
1030
|
if (value) {
|
|
771
1031
|
this.tags[forceString(key)] = forceString(value);
|
|
@@ -895,12 +1155,12 @@ function envBoolean(key, { defaultValue }) {
|
|
|
895
1155
|
lower === "no");
|
|
896
1156
|
}
|
|
897
1157
|
class JaypieLogger {
|
|
898
|
-
constructor({ level = process.env.LOG_LEVEL, tags = {}, } = {}) {
|
|
1158
|
+
constructor({ level = process.env.LOG_LEVEL, maxDepth, maxEntryBytes, maxStringLength, tags = {}, } = {}) {
|
|
899
1159
|
this._errorCount = 0;
|
|
900
1160
|
this._report = {};
|
|
901
1161
|
this._sessionActive = false;
|
|
902
1162
|
this._warnCount = 0;
|
|
903
|
-
this._params = { level, tags };
|
|
1163
|
+
this._params = { level, maxDepth, maxEntryBytes, maxStringLength, tags };
|
|
904
1164
|
this._loggers = [];
|
|
905
1165
|
this._tags = {};
|
|
906
1166
|
this._withLoggers = {};
|
|
@@ -909,6 +1169,9 @@ class JaypieLogger {
|
|
|
909
1169
|
this._logger = new Logger({
|
|
910
1170
|
format: FORMAT.JSON,
|
|
911
1171
|
level: this.level,
|
|
1172
|
+
maxDepth,
|
|
1173
|
+
maxEntryBytes,
|
|
1174
|
+
maxStringLength,
|
|
912
1175
|
tags: this._tags,
|
|
913
1176
|
});
|
|
914
1177
|
this._loggers = [this._logger];
|
|
@@ -950,6 +1213,29 @@ class JaypieLogger {
|
|
|
950
1213
|
};
|
|
951
1214
|
this.var = (messageObject, messageValue) => this._logger.var(logVar(messageObject, messageValue));
|
|
952
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Update serialization limits at runtime for this logger and all loggers
|
|
1218
|
+
* derived from it (lib, with, flag). Pass a number to set a limit,
|
|
1219
|
+
* `false` to disable one; omitted keys are unchanged. Persists across
|
|
1220
|
+
* init().
|
|
1221
|
+
*/
|
|
1222
|
+
config(options = {}) {
|
|
1223
|
+
if (options.maxDepth !== undefined) {
|
|
1224
|
+
this._params.maxDepth = options.maxDepth;
|
|
1225
|
+
}
|
|
1226
|
+
if (options.maxEntryBytes !== undefined) {
|
|
1227
|
+
this._params.maxEntryBytes = options.maxEntryBytes;
|
|
1228
|
+
}
|
|
1229
|
+
if (options.maxStringLength !== undefined) {
|
|
1230
|
+
this._params.maxStringLength = options.maxStringLength;
|
|
1231
|
+
}
|
|
1232
|
+
for (const logger of this._loggers) {
|
|
1233
|
+
logger.config(options);
|
|
1234
|
+
}
|
|
1235
|
+
for (const key of Object.keys(this._withLoggers)) {
|
|
1236
|
+
this._withLoggers[key].config(options);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
953
1239
|
flag(flag) {
|
|
954
1240
|
if (typeof flag !== "string" || flag === "") {
|
|
955
1241
|
return this;
|
|
@@ -970,6 +1256,9 @@ class JaypieLogger {
|
|
|
970
1256
|
this._logger = new Logger({
|
|
971
1257
|
format: FORMAT.JSON,
|
|
972
1258
|
level: this.level,
|
|
1259
|
+
maxDepth: this._params.maxDepth,
|
|
1260
|
+
maxEntryBytes: this._params.maxEntryBytes,
|
|
1261
|
+
maxStringLength: this._params.maxStringLength,
|
|
973
1262
|
tags: this._tags,
|
|
974
1263
|
});
|
|
975
1264
|
this._loggers = [this._logger];
|
|
@@ -1052,6 +1341,9 @@ class JaypieLogger {
|
|
|
1052
1341
|
}
|
|
1053
1342
|
return LEVEL.SILENT;
|
|
1054
1343
|
})(),
|
|
1344
|
+
maxDepth: this._params.maxDepth,
|
|
1345
|
+
maxEntryBytes: this._params.maxEntryBytes,
|
|
1346
|
+
maxStringLength: this._params.maxStringLength,
|
|
1055
1347
|
tags: newTags,
|
|
1056
1348
|
});
|
|
1057
1349
|
this._loggers.push(logger._logger);
|
|
@@ -1134,6 +1426,9 @@ class JaypieLogger {
|
|
|
1134
1426
|
}
|
|
1135
1427
|
const logger = new JaypieLogger({
|
|
1136
1428
|
level: this.level,
|
|
1429
|
+
maxDepth: this._params.maxDepth,
|
|
1430
|
+
maxEntryBytes: this._params.maxEntryBytes,
|
|
1431
|
+
maxStringLength: this._params.maxStringLength,
|
|
1137
1432
|
tags: { ...this._tags },
|
|
1138
1433
|
});
|
|
1139
1434
|
logger._logger = this._logger.with(key, value);
|