@jaypie/logger 1.2.18 → 1.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/cjs/index.cjs +319 -7
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +53 -5
  4. package/dist/cjs/src/JaypieLogger.d.ts +64 -0
  5. package/dist/cjs/{Logger.d.ts → src/Logger.d.ts} +9 -2
  6. package/dist/cjs/{constants.d.ts → src/constants.d.ts} +6 -0
  7. package/dist/cjs/{index.d.ts → src/index.d.ts} +3 -1
  8. package/dist/cjs/src/limits.d.ts +55 -0
  9. package/dist/esm/index.d.ts +53 -5
  10. package/dist/esm/index.js +319 -8
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/src/JaypieLogger.d.ts +64 -0
  13. package/dist/esm/{Logger.d.ts → src/Logger.d.ts} +9 -2
  14. package/dist/esm/src/__tests__/limits.spec.d.ts +1 -0
  15. package/dist/esm/src/__tests__/sanitizeAuth.spec.d.ts +1 -0
  16. package/dist/esm/{constants.d.ts → src/constants.d.ts} +6 -0
  17. package/dist/esm/src/index.d.ts +10 -0
  18. package/dist/esm/src/limits.d.ts +55 -0
  19. package/package.json +1 -1
  20. package/dist/cjs/JaypieLogger.d.ts +0 -40
  21. package/dist/esm/JaypieLogger.d.ts +0 -40
  22. /package/dist/cjs/{__tests__ → src/__tests__}/datadogTransport.spec.d.ts +0 -0
  23. /package/dist/cjs/{__tests__ → src/__tests__}/index.spec.d.ts +0 -0
  24. /package/dist/cjs/{__tests__/sanitizeAuth.spec.d.ts → src/__tests__/limits.spec.d.ts} +0 -0
  25. /package/dist/{esm → cjs/src}/__tests__/sanitizeAuth.spec.d.ts +0 -0
  26. /package/dist/cjs/{datadogTransport.d.ts → src/datadogTransport.d.ts} +0 -0
  27. /package/dist/cjs/{forceVar.d.ts → src/forceVar.d.ts} +0 -0
  28. /package/dist/cjs/{logTags.d.ts → src/logTags.d.ts} +0 -0
  29. /package/dist/cjs/{logVar.d.ts → src/logVar.d.ts} +0 -0
  30. /package/dist/cjs/{pipelines.d.ts → src/pipelines.d.ts} +0 -0
  31. /package/dist/cjs/{sanitizeAuth.d.ts → src/sanitizeAuth.d.ts} +0 -0
  32. /package/dist/cjs/{utils.d.ts → src/utils.d.ts} +0 -0
  33. /package/dist/esm/{__tests__ → src/__tests__}/datadogTransport.spec.d.ts +0 -0
  34. /package/dist/esm/{__tests__ → src/__tests__}/index.spec.d.ts +0 -0
  35. /package/dist/esm/{datadogTransport.d.ts → src/datadogTransport.d.ts} +0 -0
  36. /package/dist/esm/{forceVar.d.ts → src/forceVar.d.ts} +0 -0
  37. /package/dist/esm/{logTags.d.ts → src/logTags.d.ts} +0 -0
  38. /package/dist/esm/{logVar.d.ts → src/logVar.d.ts} +0 -0
  39. /package/dist/esm/{pipelines.d.ts → src/pipelines.d.ts} +0 -0
  40. /package/dist/esm/{sanitizeAuth.d.ts → src/sanitizeAuth.d.ts} +0 -0
  41. /package/dist/esm/{utils.d.ts → src/utils.d.ts} +0 -0
@@ -4,5 +4,7 @@ import { FORMAT, LEVEL } from "./constants";
4
4
  import { _resetDatadogTransport, getDatadogTransport, isDatadogForwardingEnabled } from "./datadogTransport";
5
5
  import { redactAuth, sanitizeAuth } from "./sanitizeAuth";
6
6
  export { FORMAT, LEVEL, Logger, _resetDatadogTransport, createLogger, getDatadogTransport, isDatadogForwardingEnabled, redactAuth, sanitizeAuth, };
7
- export declare const log: import("./JaypieLogger").default;
7
+ export type { SerializationLimitOptions, SerializationLimits } from "./limits";
8
+ export { JaypieLogger } from "./JaypieLogger";
9
+ export declare const log: import("./JaypieLogger").JaypieLogger;
8
10
  export default log;
@@ -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>;
@@ -1,7 +1,25 @@
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
+
1
19
  type LogLevel = string;
2
20
  type LogFormat = "json" | "text";
3
21
  type Tags = Record<string, string>;
4
- interface LoggerOptions {
22
+ interface LoggerOptions extends SerializationLimitOptions {
5
23
  format?: LogFormat;
6
24
  level?: LogLevel;
7
25
  levelField?: boolean | string;
@@ -23,14 +41,20 @@ declare class Logger {
23
41
  var: (messageObject: unknown, messageValue?: unknown) => void;
24
42
  warn: LogMethod;
25
43
  private levelField;
26
- constructor({ format, level, levelField, tags, varLevel, }?: LoggerOptions);
44
+ private limits;
45
+ constructor({ format, level, levelField, maxDepth, maxEntryBytes, maxStringLength, tags, varLevel, }?: LoggerOptions);
27
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;
28
52
  tag(key: unknown, value?: unknown): void;
29
53
  untag(key: unknown): void;
30
54
  with(key: unknown, value?: unknown): Logger;
31
55
  }
32
56
 
33
- interface JaypieLoggerOptions {
57
+ interface JaypieLoggerOptions extends SerializationLimitOptions {
34
58
  level?: string;
35
59
  tags?: Record<string, string>;
36
60
  }
@@ -52,7 +76,14 @@ declare class JaypieLogger {
52
76
  private _tags;
53
77
  private _warnCount;
54
78
  private _withLoggers;
55
- constructor({ level, tags, }?: JaypieLoggerOptions);
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;
56
87
  flag(flag?: string): JaypieLogger;
57
88
  init(): void;
58
89
  lib({ level, lib, tags, }?: {
@@ -60,9 +91,25 @@ declare class JaypieLogger {
60
91
  lib?: string;
61
92
  tags?: Record<string, string>;
62
93
  }): JaypieLogger;
94
+ /**
95
+ * Merge data into the current session's report. Requires an active
96
+ * session (started via setup()); logs a warning and is a no-op otherwise.
97
+ * Warns when overwriting an existing key. Emitted by teardown().
98
+ */
63
99
  report(data: Record<string, unknown>): void;
100
+ /**
101
+ * Start a report session: resets warn/error counters and accumulated
102
+ * report data, applies optional tags. Pair with teardown() to bookend a
103
+ * request. Handlers call this automatically.
104
+ */
64
105
  setup(tags?: Record<string, unknown>): void;
65
106
  tag(tags: Record<string, unknown>): void;
107
+ /**
108
+ * End the current report session: emits log.info.var({ report }) with
109
+ * accumulated report() data plus { log: { warn, warns, error, errors } }
110
+ * counts, then resets session state. No-op if no session is active.
111
+ * Handlers call this automatically.
112
+ */
66
113
  teardown(): void;
67
114
  untag(key: unknown): void;
68
115
  with(key: unknown, value?: unknown): JaypieLogger;
@@ -107,4 +154,5 @@ declare function sanitizeAuth(value: unknown): unknown;
107
154
 
108
155
  declare const log: JaypieLogger;
109
156
 
110
- export { FORMAT, LEVEL, Logger, _resetDatadogTransport, createLogger, log as default, getDatadogTransport, isDatadogForwardingEnabled, log, redactAuth, sanitizeAuth };
157
+ export { FORMAT, JaypieLogger, LEVEL, Logger, _resetDatadogTransport, createLogger, log as default, getDatadogTransport, isDatadogForwardingEnabled, log, redactAuth, sanitizeAuth };
158
+ 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
- const sanitized = messages.map(sanitizeAuth);
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
- out(json, { level: logLevel });
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
- const message = stringify(...sanitized);
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
- out(json, { level: logLevel });
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,11 +1341,19 @@ 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);
1058
1350
  return logger;
1059
1351
  }
1352
+ /**
1353
+ * Merge data into the current session's report. Requires an active
1354
+ * session (started via setup()); logs a warning and is a no-op otherwise.
1355
+ * Warns when overwriting an existing key. Emitted by teardown().
1356
+ */
1060
1357
  report(data) {
1061
1358
  if (!this._sessionActive) {
1062
1359
  this.warn("[logger] report() called without active session");
@@ -1069,6 +1366,11 @@ class JaypieLogger {
1069
1366
  }
1070
1367
  Object.assign(this._report, data);
1071
1368
  }
1369
+ /**
1370
+ * Start a report session: resets warn/error counters and accumulated
1371
+ * report data, applies optional tags. Pair with teardown() to bookend a
1372
+ * request. Handlers call this automatically.
1373
+ */
1072
1374
  setup(tags) {
1073
1375
  if (this._sessionActive) {
1074
1376
  this.warn("[logger] setup() called while session already active");
@@ -1087,6 +1389,12 @@ class JaypieLogger {
1087
1389
  }
1088
1390
  Object.assign(this._tags, tags);
1089
1391
  }
1392
+ /**
1393
+ * End the current report session: emits log.info.var({ report }) with
1394
+ * accumulated report() data plus { log: { warn, warns, error, errors } }
1395
+ * counts, then resets session state. No-op if no session is active.
1396
+ * Handlers call this automatically.
1397
+ */
1090
1398
  teardown() {
1091
1399
  if (!this._sessionActive) {
1092
1400
  return;
@@ -1134,6 +1442,9 @@ class JaypieLogger {
1134
1442
  }
1135
1443
  const logger = new JaypieLogger({
1136
1444
  level: this.level,
1445
+ maxDepth: this._params.maxDepth,
1446
+ maxEntryBytes: this._params.maxEntryBytes,
1447
+ maxStringLength: this._params.maxStringLength,
1137
1448
  tags: { ...this._tags },
1138
1449
  });
1139
1450
  logger._logger = this._logger.with(key, value);
@@ -1152,5 +1463,5 @@ function createLogger(tags = {}) {
1152
1463
 
1153
1464
  const log = createLogger();
1154
1465
 
1155
- export { FORMAT, LEVEL, Logger, _resetDatadogTransport, createLogger, log as default, getDatadogTransport, isDatadogForwardingEnabled, log, redactAuth, sanitizeAuth };
1466
+ export { FORMAT, JaypieLogger, LEVEL, Logger, _resetDatadogTransport, createLogger, log as default, getDatadogTransport, isDatadogForwardingEnabled, log, redactAuth, sanitizeAuth };
1156
1467
  //# sourceMappingURL=index.js.map