@logtape/logtape 1.0.0-dev.246 → 1.0.0-dev.248
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/deno.json +1 -1
- package/dist/formatter.cjs +187 -18
- package/dist/formatter.d.cts.map +1 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +187 -18
- package/dist/formatter.js.map +1 -1
- package/dist/logger.cjs +26 -20
- package/dist/logger.js +26 -20
- package/dist/logger.js.map +1 -1
- package/dist/sink.cjs +107 -10
- package/dist/sink.d.cts +71 -2
- package/dist/sink.d.cts.map +1 -1
- package/dist/sink.d.ts +71 -2
- package/dist/sink.d.ts.map +1 -1
- package/dist/sink.js +107 -10
- package/dist/sink.js.map +1 -1
- package/formatter.ts +273 -68
- package/logger.ts +61 -30
- package/package.json +2 -1
- package/sink.test.ts +424 -5
- package/sink.ts +245 -13
package/formatter.ts
CHANGED
|
@@ -209,6 +209,130 @@ export interface TextFormatterOptions {
|
|
|
209
209
|
format?: (values: FormattedValues) => string;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// Optimized helper functions for timestamp formatting
|
|
213
|
+
function padZero(num: number): string {
|
|
214
|
+
return num < 10 ? `0${num}` : `${num}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function padThree(num: number): string {
|
|
218
|
+
return num < 10 ? `00${num}` : num < 100 ? `0${num}` : `${num}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Pre-optimized timestamp formatter functions
|
|
222
|
+
const timestampFormatters = {
|
|
223
|
+
"date-time-timezone": (ts: number): string => {
|
|
224
|
+
const d = new Date(ts);
|
|
225
|
+
const year = d.getUTCFullYear();
|
|
226
|
+
const month = padZero(d.getUTCMonth() + 1);
|
|
227
|
+
const day = padZero(d.getUTCDate());
|
|
228
|
+
const hour = padZero(d.getUTCHours());
|
|
229
|
+
const minute = padZero(d.getUTCMinutes());
|
|
230
|
+
const second = padZero(d.getUTCSeconds());
|
|
231
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
232
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}.${ms} +00:00`;
|
|
233
|
+
},
|
|
234
|
+
"date-time-tz": (ts: number): string => {
|
|
235
|
+
const d = new Date(ts);
|
|
236
|
+
const year = d.getUTCFullYear();
|
|
237
|
+
const month = padZero(d.getUTCMonth() + 1);
|
|
238
|
+
const day = padZero(d.getUTCDate());
|
|
239
|
+
const hour = padZero(d.getUTCHours());
|
|
240
|
+
const minute = padZero(d.getUTCMinutes());
|
|
241
|
+
const second = padZero(d.getUTCSeconds());
|
|
242
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
243
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}.${ms} +00`;
|
|
244
|
+
},
|
|
245
|
+
"date-time": (ts: number): string => {
|
|
246
|
+
const d = new Date(ts);
|
|
247
|
+
const year = d.getUTCFullYear();
|
|
248
|
+
const month = padZero(d.getUTCMonth() + 1);
|
|
249
|
+
const day = padZero(d.getUTCDate());
|
|
250
|
+
const hour = padZero(d.getUTCHours());
|
|
251
|
+
const minute = padZero(d.getUTCMinutes());
|
|
252
|
+
const second = padZero(d.getUTCSeconds());
|
|
253
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
254
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}.${ms}`;
|
|
255
|
+
},
|
|
256
|
+
"time-timezone": (ts: number): string => {
|
|
257
|
+
const d = new Date(ts);
|
|
258
|
+
const hour = padZero(d.getUTCHours());
|
|
259
|
+
const minute = padZero(d.getUTCMinutes());
|
|
260
|
+
const second = padZero(d.getUTCSeconds());
|
|
261
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
262
|
+
return `${hour}:${minute}:${second}.${ms} +00:00`;
|
|
263
|
+
},
|
|
264
|
+
"time-tz": (ts: number): string => {
|
|
265
|
+
const d = new Date(ts);
|
|
266
|
+
const hour = padZero(d.getUTCHours());
|
|
267
|
+
const minute = padZero(d.getUTCMinutes());
|
|
268
|
+
const second = padZero(d.getUTCSeconds());
|
|
269
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
270
|
+
return `${hour}:${minute}:${second}.${ms} +00`;
|
|
271
|
+
},
|
|
272
|
+
"time": (ts: number): string => {
|
|
273
|
+
const d = new Date(ts);
|
|
274
|
+
const hour = padZero(d.getUTCHours());
|
|
275
|
+
const minute = padZero(d.getUTCMinutes());
|
|
276
|
+
const second = padZero(d.getUTCSeconds());
|
|
277
|
+
const ms = padThree(d.getUTCMilliseconds());
|
|
278
|
+
return `${hour}:${minute}:${second}.${ms}`;
|
|
279
|
+
},
|
|
280
|
+
"date": (ts: number): string => {
|
|
281
|
+
const d = new Date(ts);
|
|
282
|
+
const year = d.getUTCFullYear();
|
|
283
|
+
const month = padZero(d.getUTCMonth() + 1);
|
|
284
|
+
const day = padZero(d.getUTCDate());
|
|
285
|
+
return `${year}-${month}-${day}`;
|
|
286
|
+
},
|
|
287
|
+
"rfc3339": (ts: number): string => new Date(ts).toISOString(),
|
|
288
|
+
"none": (): null => null,
|
|
289
|
+
} as const;
|
|
290
|
+
|
|
291
|
+
// Pre-computed level renderers for common cases
|
|
292
|
+
const levelRenderersCache = {
|
|
293
|
+
ABBR: levelAbbreviations,
|
|
294
|
+
abbr: {
|
|
295
|
+
trace: "trc",
|
|
296
|
+
debug: "dbg",
|
|
297
|
+
info: "inf",
|
|
298
|
+
warning: "wrn",
|
|
299
|
+
error: "err",
|
|
300
|
+
fatal: "ftl",
|
|
301
|
+
} as const,
|
|
302
|
+
FULL: {
|
|
303
|
+
trace: "TRACE",
|
|
304
|
+
debug: "DEBUG",
|
|
305
|
+
info: "INFO",
|
|
306
|
+
warning: "WARNING",
|
|
307
|
+
error: "ERROR",
|
|
308
|
+
fatal: "FATAL",
|
|
309
|
+
} as const,
|
|
310
|
+
full: {
|
|
311
|
+
trace: "trace",
|
|
312
|
+
debug: "debug",
|
|
313
|
+
info: "info",
|
|
314
|
+
warning: "warning",
|
|
315
|
+
error: "error",
|
|
316
|
+
fatal: "fatal",
|
|
317
|
+
} as const,
|
|
318
|
+
L: {
|
|
319
|
+
trace: "T",
|
|
320
|
+
debug: "D",
|
|
321
|
+
info: "I",
|
|
322
|
+
warning: "W",
|
|
323
|
+
error: "E",
|
|
324
|
+
fatal: "F",
|
|
325
|
+
} as const,
|
|
326
|
+
l: {
|
|
327
|
+
trace: "t",
|
|
328
|
+
debug: "d",
|
|
329
|
+
info: "i",
|
|
330
|
+
warning: "w",
|
|
331
|
+
error: "e",
|
|
332
|
+
fatal: "f",
|
|
333
|
+
} as const,
|
|
334
|
+
} as const;
|
|
335
|
+
|
|
212
336
|
/**
|
|
213
337
|
* Get a text formatter with the specified options. Although it's flexible
|
|
214
338
|
* enough to create a custom formatter, if you want more control, you can
|
|
@@ -229,61 +353,81 @@ export interface TextFormatterOptions {
|
|
|
229
353
|
export function getTextFormatter(
|
|
230
354
|
options: TextFormatterOptions = {},
|
|
231
355
|
): TextFormatter {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
: options.timestamp === "time"
|
|
249
|
-
? (ts: number): string =>
|
|
250
|
-
new Date(ts).toISOString().replace(/.*T/, "").replace("Z", "")
|
|
251
|
-
: options.timestamp === "date"
|
|
252
|
-
? (ts: number): string => new Date(ts).toISOString().replace(/T.*/, "")
|
|
253
|
-
: options.timestamp === "rfc3339"
|
|
254
|
-
? (ts: number): string => new Date(ts).toISOString()
|
|
255
|
-
: options.timestamp === "none" || options.timestamp === "disabled"
|
|
256
|
-
? () => null
|
|
257
|
-
: options.timestamp;
|
|
356
|
+
// Pre-compute timestamp formatter with optimized lookup
|
|
357
|
+
const timestampRenderer = (() => {
|
|
358
|
+
const tsOption = options.timestamp;
|
|
359
|
+
if (tsOption == null) {
|
|
360
|
+
return timestampFormatters["date-time-timezone"];
|
|
361
|
+
} else if (tsOption === "disabled") {
|
|
362
|
+
return timestampFormatters["none"];
|
|
363
|
+
} else if (
|
|
364
|
+
typeof tsOption === "string" && tsOption in timestampFormatters
|
|
365
|
+
) {
|
|
366
|
+
return timestampFormatters[tsOption as keyof typeof timestampFormatters];
|
|
367
|
+
} else {
|
|
368
|
+
return tsOption as (ts: number) => string | null;
|
|
369
|
+
}
|
|
370
|
+
})();
|
|
371
|
+
|
|
258
372
|
const categorySeparator = options.category ?? "·";
|
|
259
373
|
const valueRenderer = options.value ?? inspect;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
374
|
+
|
|
375
|
+
// Pre-compute level renderer for better performance
|
|
376
|
+
const levelRenderer = (() => {
|
|
377
|
+
const levelOption = options.level;
|
|
378
|
+
if (levelOption == null || levelOption === "ABBR") {
|
|
379
|
+
return (level: LogLevel): string => levelRenderersCache.ABBR[level];
|
|
380
|
+
} else if (levelOption === "abbr") {
|
|
381
|
+
return (level: LogLevel): string => levelRenderersCache.abbr[level];
|
|
382
|
+
} else if (levelOption === "FULL") {
|
|
383
|
+
return (level: LogLevel): string => levelRenderersCache.FULL[level];
|
|
384
|
+
} else if (levelOption === "full") {
|
|
385
|
+
return (level: LogLevel): string => levelRenderersCache.full[level];
|
|
386
|
+
} else if (levelOption === "L") {
|
|
387
|
+
return (level: LogLevel): string => levelRenderersCache.L[level];
|
|
388
|
+
} else if (levelOption === "l") {
|
|
389
|
+
return (level: LogLevel): string => levelRenderersCache.l[level];
|
|
390
|
+
} else {
|
|
391
|
+
return levelOption;
|
|
392
|
+
}
|
|
393
|
+
})();
|
|
394
|
+
|
|
273
395
|
const formatter: (values: FormattedValues) => string = options.format ??
|
|
274
396
|
(({ timestamp, level, category, message }: FormattedValues) =>
|
|
275
397
|
`${timestamp ? `${timestamp} ` : ""}[${level}] ${category}: ${message}`);
|
|
398
|
+
|
|
276
399
|
return (record: LogRecord): string => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
400
|
+
// Optimized message building
|
|
401
|
+
const msgParts = record.message;
|
|
402
|
+
const msgLen = msgParts.length;
|
|
403
|
+
|
|
404
|
+
let message: string;
|
|
405
|
+
if (msgLen === 1) {
|
|
406
|
+
// Fast path for simple messages with no interpolation
|
|
407
|
+
message = msgParts[0] as string;
|
|
408
|
+
} else if (msgLen <= 6) {
|
|
409
|
+
// Fast path for small messages - direct concatenation
|
|
410
|
+
message = "";
|
|
411
|
+
for (let i = 0; i < msgLen; i++) {
|
|
412
|
+
message += (i % 2 === 0) ? msgParts[i] : valueRenderer(msgParts[i]);
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// Optimized path for larger messages - array join
|
|
416
|
+
const parts: string[] = new Array(msgLen);
|
|
417
|
+
for (let i = 0; i < msgLen; i++) {
|
|
418
|
+
parts[i] = (i % 2 === 0)
|
|
419
|
+
? msgParts[i] as string
|
|
420
|
+
: valueRenderer(msgParts[i]);
|
|
421
|
+
}
|
|
422
|
+
message = parts.join("");
|
|
281
423
|
}
|
|
424
|
+
|
|
282
425
|
const timestamp = timestampRenderer(record.timestamp);
|
|
283
426
|
const level = levelRenderer(record.level);
|
|
284
427
|
const category = typeof categorySeparator === "function"
|
|
285
428
|
? categorySeparator(record.category)
|
|
286
429
|
: record.category.join(categorySeparator);
|
|
430
|
+
|
|
287
431
|
const values: FormattedValues = {
|
|
288
432
|
timestamp,
|
|
289
433
|
level,
|
|
@@ -574,6 +718,58 @@ export interface JsonLinesFormatterOptions {
|
|
|
574
718
|
export function getJsonLinesFormatter(
|
|
575
719
|
options: JsonLinesFormatterOptions = {},
|
|
576
720
|
): TextFormatter {
|
|
721
|
+
// Most common configuration - optimize for the default case
|
|
722
|
+
if (!options.categorySeparator && !options.message && !options.properties) {
|
|
723
|
+
// Ultra-minimalist path - eliminate all possible overhead
|
|
724
|
+
return (record: LogRecord): string => {
|
|
725
|
+
// Direct benchmark pattern match (most common case first)
|
|
726
|
+
if (record.message.length === 3) {
|
|
727
|
+
return JSON.stringify({
|
|
728
|
+
"@timestamp": new Date(record.timestamp).toISOString(),
|
|
729
|
+
level: record.level === "warning"
|
|
730
|
+
? "WARN"
|
|
731
|
+
: record.level.toUpperCase(),
|
|
732
|
+
message: record.message[0] + JSON.stringify(record.message[1]) +
|
|
733
|
+
record.message[2],
|
|
734
|
+
logger: record.category.join("."),
|
|
735
|
+
properties: record.properties,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Single message (second most common)
|
|
740
|
+
if (record.message.length === 1) {
|
|
741
|
+
return JSON.stringify({
|
|
742
|
+
"@timestamp": new Date(record.timestamp).toISOString(),
|
|
743
|
+
level: record.level === "warning"
|
|
744
|
+
? "WARN"
|
|
745
|
+
: record.level.toUpperCase(),
|
|
746
|
+
message: record.message[0],
|
|
747
|
+
logger: record.category.join("."),
|
|
748
|
+
properties: record.properties,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Complex messages (fallback)
|
|
753
|
+
let msg = record.message[0] as string;
|
|
754
|
+
for (let i = 1; i < record.message.length; i++) {
|
|
755
|
+
msg += (i & 1) ? JSON.stringify(record.message[i]) : record.message[i];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return JSON.stringify({
|
|
759
|
+
"@timestamp": new Date(record.timestamp).toISOString(),
|
|
760
|
+
level: record.level === "warning" ? "WARN" : record.level.toUpperCase(),
|
|
761
|
+
message: msg,
|
|
762
|
+
logger: record.category.join("."),
|
|
763
|
+
properties: record.properties,
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Pre-compile configuration for non-default cases
|
|
769
|
+
const isTemplateMessage = options.message === "template";
|
|
770
|
+
const propertiesOption = options.properties ?? "nest:properties";
|
|
771
|
+
|
|
772
|
+
// Pre-compile category joining strategy
|
|
577
773
|
let joinCategory: (category: readonly string[]) => string | readonly string[];
|
|
578
774
|
if (typeof options.categorySeparator === "function") {
|
|
579
775
|
joinCategory = options.categorySeparator;
|
|
@@ -583,34 +779,11 @@ export function getJsonLinesFormatter(
|
|
|
583
779
|
category.join(separator);
|
|
584
780
|
}
|
|
585
781
|
|
|
586
|
-
|
|
587
|
-
if (options.message === "template") {
|
|
588
|
-
getMessage = (record: LogRecord): string => {
|
|
589
|
-
if (typeof record.rawMessage === "string") {
|
|
590
|
-
return record.rawMessage;
|
|
591
|
-
}
|
|
592
|
-
let msg = "";
|
|
593
|
-
for (let i = 0; i < record.rawMessage.length; i++) {
|
|
594
|
-
msg += i % 2 < 1 ? record.rawMessage[i] : "{}";
|
|
595
|
-
}
|
|
596
|
-
return msg;
|
|
597
|
-
};
|
|
598
|
-
} else {
|
|
599
|
-
getMessage = (record: LogRecord): string => {
|
|
600
|
-
let msg = "";
|
|
601
|
-
for (let i = 0; i < record.message.length; i++) {
|
|
602
|
-
msg += i % 2 < 1
|
|
603
|
-
? record.message[i]
|
|
604
|
-
: JSON.stringify(record.message[i]);
|
|
605
|
-
}
|
|
606
|
-
return msg;
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const propertiesOption = options.properties ?? "nest:properties";
|
|
782
|
+
// Pre-compile properties handling strategy
|
|
611
783
|
let getProperties: (
|
|
612
784
|
properties: Record<string, unknown>,
|
|
613
785
|
) => Record<string, unknown>;
|
|
786
|
+
|
|
614
787
|
if (propertiesOption === "flatten") {
|
|
615
788
|
getProperties = (properties) => properties;
|
|
616
789
|
} else if (propertiesOption.startsWith("prepend:")) {
|
|
@@ -640,6 +813,38 @@ export function getJsonLinesFormatter(
|
|
|
640
813
|
);
|
|
641
814
|
}
|
|
642
815
|
|
|
816
|
+
// Pre-compile message rendering function
|
|
817
|
+
let getMessage: (record: LogRecord) => string;
|
|
818
|
+
|
|
819
|
+
if (isTemplateMessage) {
|
|
820
|
+
getMessage = (record: LogRecord): string => {
|
|
821
|
+
if (typeof record.rawMessage === "string") {
|
|
822
|
+
return record.rawMessage;
|
|
823
|
+
}
|
|
824
|
+
let msg = "";
|
|
825
|
+
for (let i = 0; i < record.rawMessage.length; i++) {
|
|
826
|
+
msg += i % 2 < 1 ? record.rawMessage[i] : "{}";
|
|
827
|
+
}
|
|
828
|
+
return msg;
|
|
829
|
+
};
|
|
830
|
+
} else {
|
|
831
|
+
getMessage = (record: LogRecord): string => {
|
|
832
|
+
const msgLen = record.message.length;
|
|
833
|
+
|
|
834
|
+
if (msgLen === 1) {
|
|
835
|
+
return record.message[0] as string;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
let msg = "";
|
|
839
|
+
for (let i = 0; i < msgLen; i++) {
|
|
840
|
+
msg += (i % 2 < 1)
|
|
841
|
+
? record.message[i]
|
|
842
|
+
: JSON.stringify(record.message[i]);
|
|
843
|
+
}
|
|
844
|
+
return msg;
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
643
848
|
return (record: LogRecord): string => {
|
|
644
849
|
return JSON.stringify({
|
|
645
850
|
"@timestamp": new Date(record.timestamp).toISOString(),
|
package/logger.ts
CHANGED
|
@@ -1343,46 +1343,77 @@ export function parseMessageTemplate(
|
|
|
1343
1343
|
template: string,
|
|
1344
1344
|
properties: Record<string, unknown>,
|
|
1345
1345
|
): readonly unknown[] {
|
|
1346
|
+
const length = template.length;
|
|
1347
|
+
if (length === 0) return [""];
|
|
1348
|
+
|
|
1349
|
+
// Fast path: no placeholders
|
|
1350
|
+
if (!template.includes("{")) return [template];
|
|
1351
|
+
|
|
1346
1352
|
const message: unknown[] = [];
|
|
1347
|
-
let
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
if (char === "{"
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
//
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1353
|
+
let startIndex = 0;
|
|
1354
|
+
|
|
1355
|
+
for (let i = 0; i < length; i++) {
|
|
1356
|
+
const char = template[i];
|
|
1357
|
+
|
|
1358
|
+
if (char === "{") {
|
|
1359
|
+
const nextChar = i + 1 < length ? template[i + 1] : "";
|
|
1360
|
+
|
|
1361
|
+
if (nextChar === "{") {
|
|
1362
|
+
// Escaped { character - skip and continue
|
|
1363
|
+
i++; // Skip the next {
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Find the closing }
|
|
1368
|
+
const closeIndex = template.indexOf("}", i + 1);
|
|
1369
|
+
if (closeIndex === -1) {
|
|
1370
|
+
// No closing } found, treat as literal text
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Add text before placeholder
|
|
1375
|
+
const beforeText = template.slice(startIndex, i);
|
|
1376
|
+
message.push(beforeText.replace(/{{/g, "{").replace(/}}/g, "}"));
|
|
1377
|
+
|
|
1378
|
+
// Extract and process placeholder key
|
|
1379
|
+
const key = template.slice(i + 1, closeIndex);
|
|
1380
|
+
|
|
1381
|
+
// Resolve property value
|
|
1366
1382
|
let prop: unknown;
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1383
|
+
|
|
1384
|
+
// Check for wildcard patterns
|
|
1385
|
+
const trimmedKey = key.trim();
|
|
1386
|
+
if (trimmedKey === "*") {
|
|
1387
|
+
// This is a wildcard pattern
|
|
1388
|
+
prop = key in properties
|
|
1389
|
+
? properties[key]
|
|
1370
1390
|
: "*" in properties
|
|
1371
1391
|
? properties["*"]
|
|
1372
1392
|
: properties;
|
|
1373
|
-
} else if (part.match(/^\s|\s$/)) {
|
|
1374
|
-
prop = part in properties ? properties[part] : properties[part.trim()];
|
|
1375
1393
|
} else {
|
|
1376
|
-
|
|
1394
|
+
// Regular property lookup with possible whitespace handling
|
|
1395
|
+
if (key !== trimmedKey) {
|
|
1396
|
+
// Key has leading/trailing whitespace
|
|
1397
|
+
prop = key in properties ? properties[key] : properties[trimmedKey];
|
|
1398
|
+
} else {
|
|
1399
|
+
// Key has no leading/trailing whitespace
|
|
1400
|
+
prop = properties[key];
|
|
1401
|
+
}
|
|
1377
1402
|
}
|
|
1403
|
+
|
|
1378
1404
|
message.push(prop);
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1405
|
+
i = closeIndex; // Move to the }
|
|
1406
|
+
startIndex = i + 1;
|
|
1407
|
+
} else if (char === "}" && i + 1 < length && template[i + 1] === "}") {
|
|
1408
|
+
// Escaped } character - skip
|
|
1409
|
+
i++; // Skip the next }
|
|
1383
1410
|
}
|
|
1384
1411
|
}
|
|
1385
|
-
|
|
1412
|
+
|
|
1413
|
+
// Add remaining text
|
|
1414
|
+
const remainingText = template.slice(startIndex);
|
|
1415
|
+
message.push(remainingText.replace(/{{/g, "{").replace(/}}/g, "}"));
|
|
1416
|
+
|
|
1386
1417
|
return message;
|
|
1387
1418
|
}
|
|
1388
1419
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logtape/logtape",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.248+f96c618d",
|
|
4
4
|
"description": "Simple logging library with zero dependencies for Deno/Node.js/Bun/browsers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logging",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"default": "./dist/util.js"
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
|
+
"sideEffects": false,
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@alinea/suite": "^0.6.3",
|
|
69
70
|
"@std/assert": "npm:@jsr/std__assert@^1.0.13",
|