@logtape/logtape 2.1.0-dev.564 → 2.1.0-dev.600
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/config.cjs +108 -18
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +108 -18
- package/dist/config.js.map +1 -1
- package/dist/filter.cjs +191 -0
- package/dist/filter.d.cts +138 -1
- package/dist/filter.d.cts.map +1 -1
- package/dist/filter.d.ts +138 -1
- package/dist/filter.d.ts.map +1 -1
- package/dist/filter.js +191 -1
- package/dist/filter.js.map +1 -1
- package/dist/formatter.cjs +159 -14
- package/dist/formatter.d.cts +79 -1
- package/dist/formatter.d.cts.map +1 -1
- package/dist/formatter.d.ts +79 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +157 -14
- package/dist/formatter.js.map +1 -1
- package/dist/logger.cjs +7 -0
- package/dist/logger.d.cts.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +7 -0
- package/dist/logger.js.map +1 -1
- package/dist/mod.cjs +3 -0
- package/dist/mod.d.cts +3 -3
- package/dist/mod.d.ts +3 -3
- package/dist/mod.js +3 -3
- package/dist/sink.cjs +3 -3
- package/dist/sink.js +3 -3
- package/dist/sink.js.map +1 -1
- package/package.json +1 -1
- package/skills/logtape/SKILL.md +4 -0
package/dist/filter.d.cts
CHANGED
|
@@ -11,6 +11,129 @@ import { LogRecord } from "./record.cjs";
|
|
|
11
11
|
* @returns `true` if the record should be passed to the sink.
|
|
12
12
|
*/
|
|
13
13
|
type Filter = (record: LogRecord) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Summary information emitted by {@link getThrottlingFilter} when suppressed
|
|
16
|
+
* records are reported.
|
|
17
|
+
*
|
|
18
|
+
* @since 2.1.0
|
|
19
|
+
*/
|
|
20
|
+
interface ThrottlingFilterSummary {
|
|
21
|
+
/**
|
|
22
|
+
* The throttling key whose records were suppressed.
|
|
23
|
+
*/
|
|
24
|
+
readonly key: string;
|
|
25
|
+
/**
|
|
26
|
+
* The number of records suppressed since the previous summary for the key.
|
|
27
|
+
*/
|
|
28
|
+
readonly suppressed: number;
|
|
29
|
+
/**
|
|
30
|
+
* The number of records allowed during the same summary period.
|
|
31
|
+
*/
|
|
32
|
+
readonly allowed: number;
|
|
33
|
+
/**
|
|
34
|
+
* Why the summary was emitted.
|
|
35
|
+
*/
|
|
36
|
+
readonly reason: "window" | "eviction" | "dispose";
|
|
37
|
+
/**
|
|
38
|
+
* The time at which the summary period started, in milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
readonly startTime: number;
|
|
41
|
+
/**
|
|
42
|
+
* The time at which the summary period ended, in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
readonly endTime: number;
|
|
45
|
+
/**
|
|
46
|
+
* The first record observed in the summary period.
|
|
47
|
+
*/
|
|
48
|
+
readonly firstRecord: LogRecord;
|
|
49
|
+
/**
|
|
50
|
+
* The most recent record observed in the summary period.
|
|
51
|
+
*/
|
|
52
|
+
readonly lastRecord: LogRecord;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A logger-like object used by {@link getThrottlingFilter} to emit summaries.
|
|
56
|
+
*
|
|
57
|
+
* A regular {@link Logger} returned by `getLogger()` satisfies this interface.
|
|
58
|
+
*
|
|
59
|
+
* @since 2.1.0
|
|
60
|
+
*/
|
|
61
|
+
type ThrottlingSummaryLogger = { readonly [Level in LogLevel]?: (message: string, properties: Record<string, unknown>) => void };
|
|
62
|
+
/**
|
|
63
|
+
* Summary logging options for {@link getThrottlingFilter}.
|
|
64
|
+
*
|
|
65
|
+
* @since 2.1.0
|
|
66
|
+
*/
|
|
67
|
+
interface ThrottlingFilterSummaryOptions {
|
|
68
|
+
/**
|
|
69
|
+
* The logger used to emit summary records.
|
|
70
|
+
*/
|
|
71
|
+
readonly logger: ThrottlingSummaryLogger;
|
|
72
|
+
/**
|
|
73
|
+
* The summary log level.
|
|
74
|
+
* @default `"warning"`
|
|
75
|
+
*/
|
|
76
|
+
readonly level?: LogLevel | ((summary: ThrottlingFilterSummary) => LogLevel);
|
|
77
|
+
/**
|
|
78
|
+
* The summary message.
|
|
79
|
+
* @default `"Last log message was suppressed {suppressed} times."`
|
|
80
|
+
*/
|
|
81
|
+
readonly message?: string | ((summary: ThrottlingFilterSummary) => string);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Options for {@link getThrottlingFilter}.
|
|
85
|
+
*
|
|
86
|
+
* @since 2.1.0
|
|
87
|
+
*/
|
|
88
|
+
interface ThrottlingFilterOptions {
|
|
89
|
+
/**
|
|
90
|
+
* Number of records allowed for each key in the configured window.
|
|
91
|
+
*/
|
|
92
|
+
readonly limit: number;
|
|
93
|
+
/**
|
|
94
|
+
* Window size in milliseconds.
|
|
95
|
+
*/
|
|
96
|
+
readonly windowMs: number;
|
|
97
|
+
/**
|
|
98
|
+
* Window algorithm.
|
|
99
|
+
*
|
|
100
|
+
* - `"fixed"` starts a window when the first record for a key arrives.
|
|
101
|
+
* - `"sliding"` counts records accepted during the previous `windowMs`.
|
|
102
|
+
*
|
|
103
|
+
* @default `"fixed"`
|
|
104
|
+
*/
|
|
105
|
+
readonly mode?: "fixed" | "sliding";
|
|
106
|
+
/**
|
|
107
|
+
* Source of time for window calculations.
|
|
108
|
+
*
|
|
109
|
+
* - `"clock"` uses {@link ThrottlingFilterOptions.clock}.
|
|
110
|
+
* - `"record"` uses {@link LogRecord.timestamp}.
|
|
111
|
+
*
|
|
112
|
+
* @default `"clock"`
|
|
113
|
+
*/
|
|
114
|
+
readonly timeSource?: "clock" | "record";
|
|
115
|
+
/**
|
|
116
|
+
* Clock used when {@link timeSource} is `"clock"`.
|
|
117
|
+
* @default `Date.now`
|
|
118
|
+
*/
|
|
119
|
+
readonly clock?: () => number;
|
|
120
|
+
/**
|
|
121
|
+
* Derives a throttling key from a log record. By default, records are keyed
|
|
122
|
+
* by category, level, and raw message template.
|
|
123
|
+
*/
|
|
124
|
+
readonly key?: (record: LogRecord) => string;
|
|
125
|
+
/**
|
|
126
|
+
* Maximum number of keys tracked by the filter. When the limit is reached,
|
|
127
|
+
* the least recently used key is evicted. Set to `null` to disable the cap.
|
|
128
|
+
*
|
|
129
|
+
* @default `1000`
|
|
130
|
+
*/
|
|
131
|
+
readonly maxKeys?: number | null;
|
|
132
|
+
/**
|
|
133
|
+
* Summary logging options for suppressed records.
|
|
134
|
+
*/
|
|
135
|
+
readonly summary?: ThrottlingFilterSummaryOptions;
|
|
136
|
+
}
|
|
14
137
|
/**
|
|
15
138
|
* A filter-like value is either a {@link Filter} or a {@link LogLevel}.
|
|
16
139
|
* `null` is also allowed to represent a filter that rejects all records.
|
|
@@ -31,7 +154,21 @@ declare function toFilter(filter: FilterLike): Filter;
|
|
|
31
154
|
* @returns The filter.
|
|
32
155
|
*/
|
|
33
156
|
declare function getLevelFilter(level: LogLevel | null): Filter;
|
|
157
|
+
/**
|
|
158
|
+
* Returns a stateful filter that rate-limits repeated log records.
|
|
159
|
+
*
|
|
160
|
+
* The default key treats records with the same category, level, and raw
|
|
161
|
+
* message template as identical, ignoring substituted values. If summary
|
|
162
|
+
* logging is enabled, the filter logs summaries as a side effect when
|
|
163
|
+
* suppression ends, when a suppressed key is evicted, or when the filter is
|
|
164
|
+
* disposed.
|
|
165
|
+
*
|
|
166
|
+
* @param options Throttling options.
|
|
167
|
+
* @returns A throttling filter.
|
|
168
|
+
* @since 2.1.0
|
|
169
|
+
*/
|
|
170
|
+
declare function getThrottlingFilter(options: ThrottlingFilterOptions): Filter & Disposable;
|
|
34
171
|
//# sourceMappingURL=filter.d.ts.map
|
|
35
172
|
//#endregion
|
|
36
|
-
export { Filter, FilterLike, getLevelFilter, toFilter };
|
|
173
|
+
export { Filter, FilterLike, ThrottlingFilterOptions, ThrottlingFilterSummary, ThrottlingFilterSummaryOptions, ThrottlingSummaryLogger, getLevelFilter, getThrottlingFilter, toFilter };
|
|
37
174
|
//# sourceMappingURL=filter.d.cts.map
|
package/dist/filter.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.cts","names":[],"sources":["../src/filter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;
|
|
1
|
+
{"version":3,"file":"filter.d.cts","names":[],"sources":["../src/filter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAQA;;;;AAuCgC,KA/CpB,MAAA,GA+CoB,CAAA,MAAA,EA/CF,SA+CE,EAAA,GAAA,OAAA;AAUhC;;;;AAGsB;AAStB;AAA+C,UA7D9B,uBAAA,CA6D8B;EAAA;;;EAYP,SAAK,GAAA,EAAA,MAAA;EAAQ;AAQb;AAQxC;EAAwC,SAAA,UAAA,EAAA,MAAA;EAAA;;AAsDW;EAOvC,SAAA,OAAU,EAAA,MAAA;EAAA;;;EAAoB,SAAA,MAAA,EAAA,QAAA,GAAA,UAAA,GAAA,SAAA;EAQ1B;;;EAA2B,SAAG,SAAA,EAAA,MAAA;EAAM;AAYpD;;EAA8B,SAAQ,OAAA,EAAA,MAAA;EAAQ;AAAgB;AAqD9D;EAAmC,SAAA,WAAA,EA7LX,SA6LW;EAAA;;;EAEb,SAAA,UAAA,EA1LC,SA0LD;;;;;;;;;KAhLV,uBAAA,wBACS,0CAEL;;;;;;UASC,8BAAA;;;;mBAIE;;;;;mBAOb,sBACW,4BAA4B;;;;;yCAQ5B;;;;;;;UAQA,uBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAyCS;;;;;;;;;;;qBAaL;;;;;;KAOT,UAAA,GAAa,SAAS;;;;;;;iBAQlB,QAAA,SAAiB,aAAa;;;;;;;;iBAY9B,cAAA,QAAsB,kBAAkB;;;;;;;;;;;;;;iBAqDxC,mBAAA,UACL,0BACR,SAAS"}
|
package/dist/filter.d.ts
CHANGED
|
@@ -11,6 +11,129 @@ import { LogRecord } from "./record.js";
|
|
|
11
11
|
* @returns `true` if the record should be passed to the sink.
|
|
12
12
|
*/
|
|
13
13
|
type Filter = (record: LogRecord) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Summary information emitted by {@link getThrottlingFilter} when suppressed
|
|
16
|
+
* records are reported.
|
|
17
|
+
*
|
|
18
|
+
* @since 2.1.0
|
|
19
|
+
*/
|
|
20
|
+
interface ThrottlingFilterSummary {
|
|
21
|
+
/**
|
|
22
|
+
* The throttling key whose records were suppressed.
|
|
23
|
+
*/
|
|
24
|
+
readonly key: string;
|
|
25
|
+
/**
|
|
26
|
+
* The number of records suppressed since the previous summary for the key.
|
|
27
|
+
*/
|
|
28
|
+
readonly suppressed: number;
|
|
29
|
+
/**
|
|
30
|
+
* The number of records allowed during the same summary period.
|
|
31
|
+
*/
|
|
32
|
+
readonly allowed: number;
|
|
33
|
+
/**
|
|
34
|
+
* Why the summary was emitted.
|
|
35
|
+
*/
|
|
36
|
+
readonly reason: "window" | "eviction" | "dispose";
|
|
37
|
+
/**
|
|
38
|
+
* The time at which the summary period started, in milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
readonly startTime: number;
|
|
41
|
+
/**
|
|
42
|
+
* The time at which the summary period ended, in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
readonly endTime: number;
|
|
45
|
+
/**
|
|
46
|
+
* The first record observed in the summary period.
|
|
47
|
+
*/
|
|
48
|
+
readonly firstRecord: LogRecord;
|
|
49
|
+
/**
|
|
50
|
+
* The most recent record observed in the summary period.
|
|
51
|
+
*/
|
|
52
|
+
readonly lastRecord: LogRecord;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A logger-like object used by {@link getThrottlingFilter} to emit summaries.
|
|
56
|
+
*
|
|
57
|
+
* A regular {@link Logger} returned by `getLogger()` satisfies this interface.
|
|
58
|
+
*
|
|
59
|
+
* @since 2.1.0
|
|
60
|
+
*/
|
|
61
|
+
type ThrottlingSummaryLogger = { readonly [Level in LogLevel]?: (message: string, properties: Record<string, unknown>) => void };
|
|
62
|
+
/**
|
|
63
|
+
* Summary logging options for {@link getThrottlingFilter}.
|
|
64
|
+
*
|
|
65
|
+
* @since 2.1.0
|
|
66
|
+
*/
|
|
67
|
+
interface ThrottlingFilterSummaryOptions {
|
|
68
|
+
/**
|
|
69
|
+
* The logger used to emit summary records.
|
|
70
|
+
*/
|
|
71
|
+
readonly logger: ThrottlingSummaryLogger;
|
|
72
|
+
/**
|
|
73
|
+
* The summary log level.
|
|
74
|
+
* @default `"warning"`
|
|
75
|
+
*/
|
|
76
|
+
readonly level?: LogLevel | ((summary: ThrottlingFilterSummary) => LogLevel);
|
|
77
|
+
/**
|
|
78
|
+
* The summary message.
|
|
79
|
+
* @default `"Last log message was suppressed {suppressed} times."`
|
|
80
|
+
*/
|
|
81
|
+
readonly message?: string | ((summary: ThrottlingFilterSummary) => string);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Options for {@link getThrottlingFilter}.
|
|
85
|
+
*
|
|
86
|
+
* @since 2.1.0
|
|
87
|
+
*/
|
|
88
|
+
interface ThrottlingFilterOptions {
|
|
89
|
+
/**
|
|
90
|
+
* Number of records allowed for each key in the configured window.
|
|
91
|
+
*/
|
|
92
|
+
readonly limit: number;
|
|
93
|
+
/**
|
|
94
|
+
* Window size in milliseconds.
|
|
95
|
+
*/
|
|
96
|
+
readonly windowMs: number;
|
|
97
|
+
/**
|
|
98
|
+
* Window algorithm.
|
|
99
|
+
*
|
|
100
|
+
* - `"fixed"` starts a window when the first record for a key arrives.
|
|
101
|
+
* - `"sliding"` counts records accepted during the previous `windowMs`.
|
|
102
|
+
*
|
|
103
|
+
* @default `"fixed"`
|
|
104
|
+
*/
|
|
105
|
+
readonly mode?: "fixed" | "sliding";
|
|
106
|
+
/**
|
|
107
|
+
* Source of time for window calculations.
|
|
108
|
+
*
|
|
109
|
+
* - `"clock"` uses {@link ThrottlingFilterOptions.clock}.
|
|
110
|
+
* - `"record"` uses {@link LogRecord.timestamp}.
|
|
111
|
+
*
|
|
112
|
+
* @default `"clock"`
|
|
113
|
+
*/
|
|
114
|
+
readonly timeSource?: "clock" | "record";
|
|
115
|
+
/**
|
|
116
|
+
* Clock used when {@link timeSource} is `"clock"`.
|
|
117
|
+
* @default `Date.now`
|
|
118
|
+
*/
|
|
119
|
+
readonly clock?: () => number;
|
|
120
|
+
/**
|
|
121
|
+
* Derives a throttling key from a log record. By default, records are keyed
|
|
122
|
+
* by category, level, and raw message template.
|
|
123
|
+
*/
|
|
124
|
+
readonly key?: (record: LogRecord) => string;
|
|
125
|
+
/**
|
|
126
|
+
* Maximum number of keys tracked by the filter. When the limit is reached,
|
|
127
|
+
* the least recently used key is evicted. Set to `null` to disable the cap.
|
|
128
|
+
*
|
|
129
|
+
* @default `1000`
|
|
130
|
+
*/
|
|
131
|
+
readonly maxKeys?: number | null;
|
|
132
|
+
/**
|
|
133
|
+
* Summary logging options for suppressed records.
|
|
134
|
+
*/
|
|
135
|
+
readonly summary?: ThrottlingFilterSummaryOptions;
|
|
136
|
+
}
|
|
14
137
|
/**
|
|
15
138
|
* A filter-like value is either a {@link Filter} or a {@link LogLevel}.
|
|
16
139
|
* `null` is also allowed to represent a filter that rejects all records.
|
|
@@ -31,7 +154,21 @@ declare function toFilter(filter: FilterLike): Filter;
|
|
|
31
154
|
* @returns The filter.
|
|
32
155
|
*/
|
|
33
156
|
declare function getLevelFilter(level: LogLevel | null): Filter;
|
|
157
|
+
/**
|
|
158
|
+
* Returns a stateful filter that rate-limits repeated log records.
|
|
159
|
+
*
|
|
160
|
+
* The default key treats records with the same category, level, and raw
|
|
161
|
+
* message template as identical, ignoring substituted values. If summary
|
|
162
|
+
* logging is enabled, the filter logs summaries as a side effect when
|
|
163
|
+
* suppression ends, when a suppressed key is evicted, or when the filter is
|
|
164
|
+
* disposed.
|
|
165
|
+
*
|
|
166
|
+
* @param options Throttling options.
|
|
167
|
+
* @returns A throttling filter.
|
|
168
|
+
* @since 2.1.0
|
|
169
|
+
*/
|
|
170
|
+
declare function getThrottlingFilter(options: ThrottlingFilterOptions): Filter & Disposable;
|
|
34
171
|
//# sourceMappingURL=filter.d.ts.map
|
|
35
172
|
//#endregion
|
|
36
|
-
export { Filter, FilterLike, getLevelFilter, toFilter };
|
|
173
|
+
export { Filter, FilterLike, ThrottlingFilterOptions, ThrottlingFilterSummary, ThrottlingFilterSummaryOptions, ThrottlingSummaryLogger, getLevelFilter, getThrottlingFilter, toFilter };
|
|
37
174
|
//# sourceMappingURL=filter.d.ts.map
|
package/dist/filter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.ts","names":[],"sources":["../src/filter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;
|
|
1
|
+
{"version":3,"file":"filter.d.ts","names":[],"sources":["../src/filter.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAQA;;;;AAuCgC,KA/CpB,MAAA,GA+CoB,CAAA,MAAA,EA/CF,SA+CE,EAAA,GAAA,OAAA;AAUhC;;;;AAGsB;AAStB;AAA+C,UA7D9B,uBAAA,CA6D8B;EAAA;;;EAYP,SAAK,GAAA,EAAA,MAAA;EAAQ;AAQb;AAQxC;EAAwC,SAAA,UAAA,EAAA,MAAA;EAAA;;AAsDW;EAOvC,SAAA,OAAU,EAAA,MAAA;EAAA;;;EAAoB,SAAA,MAAA,EAAA,QAAA,GAAA,UAAA,GAAA,SAAA;EAQ1B;;;EAA2B,SAAG,SAAA,EAAA,MAAA;EAAM;AAYpD;;EAA8B,SAAQ,OAAA,EAAA,MAAA;EAAQ;AAAgB;AAqD9D;EAAmC,SAAA,WAAA,EA7LX,SA6LW;EAAA;;;EAEb,SAAA,UAAA,EA1LC,SA0LD;;;;;;;;;KAhLV,uBAAA,wBACS,0CAEL;;;;;;UASC,8BAAA;;;;mBAIE;;;;;mBAOb,sBACW,4BAA4B;;;;;yCAQ5B;;;;;;;UAQA,uBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAyCS;;;;;;;;;;;qBAaL;;;;;;KAOT,UAAA,GAAa,SAAS;;;;;;;iBAQlB,QAAA,SAAiB,aAAa;;;;;;;;iBAY9B,cAAA,QAAsB,kBAAkB;;;;;;;;;;;;;;iBAqDxC,mBAAA,UACL,0BACR,SAAS"}
|
package/dist/filter.js
CHANGED
|
@@ -26,7 +26,197 @@ function getLevelFilter(level) {
|
|
|
26
26
|
else if (level === "trace") return () => true;
|
|
27
27
|
throw new TypeError(`Invalid log level: ${level}.`);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns a stateful filter that rate-limits repeated log records.
|
|
31
|
+
*
|
|
32
|
+
* The default key treats records with the same category, level, and raw
|
|
33
|
+
* message template as identical, ignoring substituted values. If summary
|
|
34
|
+
* logging is enabled, the filter logs summaries as a side effect when
|
|
35
|
+
* suppression ends, when a suppressed key is evicted, or when the filter is
|
|
36
|
+
* disposed.
|
|
37
|
+
*
|
|
38
|
+
* @param options Throttling options.
|
|
39
|
+
* @returns A throttling filter.
|
|
40
|
+
* @since 2.1.0
|
|
41
|
+
*/
|
|
42
|
+
function getThrottlingFilter(options) {
|
|
43
|
+
validatePositiveInteger("limit", options.limit);
|
|
44
|
+
validatePositiveNumber("windowMs", options.windowMs);
|
|
45
|
+
if (options.maxKeys != null) validatePositiveInteger("maxKeys", options.maxKeys);
|
|
46
|
+
const limit = options.limit;
|
|
47
|
+
const windowMs = options.windowMs;
|
|
48
|
+
const mode = options.mode ?? "fixed";
|
|
49
|
+
const timeSource = options.timeSource ?? "clock";
|
|
50
|
+
const clock = options.clock ?? Date.now;
|
|
51
|
+
const getKey = options.key ?? getDefaultThrottlingKey;
|
|
52
|
+
const maxKeys = options.maxKeys === void 0 ? 1e3 : options.maxKeys;
|
|
53
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
54
|
+
const summaryRecord = Symbol.for("LogTape.throttlingSummaryRecord");
|
|
55
|
+
let emittingSummary = false;
|
|
56
|
+
if (mode !== "fixed" && mode !== "sliding") throw new TypeError(`Invalid throttling mode: ${String(mode)}.`);
|
|
57
|
+
if (timeSource !== "clock" && timeSource !== "record") throw new TypeError(`Invalid throttling timeSource: ${String(timeSource)}.`);
|
|
58
|
+
const filter = (record) => {
|
|
59
|
+
if (emittingSummary && isSummaryRecord(record)) return true;
|
|
60
|
+
const key = getKey(record);
|
|
61
|
+
let bucket = buckets.get(key);
|
|
62
|
+
const observedTime = timeSource === "record" ? record.timestamp : clock();
|
|
63
|
+
const now = bucket == null || timeSource === "record" ? observedTime : Math.max(observedTime, bucket.lastTime);
|
|
64
|
+
if (bucket == null) {
|
|
65
|
+
evictKeysIfNeeded(now);
|
|
66
|
+
bucket = {
|
|
67
|
+
windowStart: now,
|
|
68
|
+
summaryStartTime: now,
|
|
69
|
+
acceptedAt: [],
|
|
70
|
+
allowed: 0,
|
|
71
|
+
suppressed: 0,
|
|
72
|
+
firstRecord: record,
|
|
73
|
+
lastRecord: record,
|
|
74
|
+
lastTime: now
|
|
75
|
+
};
|
|
76
|
+
buckets.set(key, bucket);
|
|
77
|
+
} else if (maxKeys != null) {
|
|
78
|
+
buckets.delete(key);
|
|
79
|
+
buckets.set(key, bucket);
|
|
80
|
+
}
|
|
81
|
+
if (mode === "fixed") return filterFixedWindow(key, bucket, record, now);
|
|
82
|
+
return filterSlidingWindow(key, bucket, record, now);
|
|
83
|
+
};
|
|
84
|
+
filter[Symbol.dispose] = () => {
|
|
85
|
+
for (const [key, bucket] of buckets) {
|
|
86
|
+
const endTime = timeSource === "record" ? bucket.lastTime : Math.max(clock(), bucket.lastTime);
|
|
87
|
+
emitSummary(key, bucket, "dispose", endTime);
|
|
88
|
+
}
|
|
89
|
+
buckets.clear();
|
|
90
|
+
};
|
|
91
|
+
return filter;
|
|
92
|
+
function filterFixedWindow(key, bucket, record, now) {
|
|
93
|
+
if (now - bucket.windowStart >= windowMs) {
|
|
94
|
+
emitSummary(key, bucket, "window", now);
|
|
95
|
+
bucket.windowStart = now;
|
|
96
|
+
bucket.summaryStartTime = now;
|
|
97
|
+
bucket.acceptedAt.length = 0;
|
|
98
|
+
bucket.allowed = 0;
|
|
99
|
+
bucket.suppressed = 0;
|
|
100
|
+
bucket.firstRecord = record;
|
|
101
|
+
}
|
|
102
|
+
const allowed = bucket.allowed < limit;
|
|
103
|
+
if (allowed) bucket.allowed++;
|
|
104
|
+
else bucket.suppressed++;
|
|
105
|
+
bucket.lastRecord = record;
|
|
106
|
+
bucket.lastTime = now;
|
|
107
|
+
return allowed;
|
|
108
|
+
}
|
|
109
|
+
function filterSlidingWindow(key, bucket, record, now) {
|
|
110
|
+
pruneExpiredAcceptedAt(bucket, now);
|
|
111
|
+
const allowed = bucket.acceptedAt.length < limit;
|
|
112
|
+
if (allowed) {
|
|
113
|
+
if (bucket.suppressed > 0) {
|
|
114
|
+
emitSummary(key, bucket, "window", now);
|
|
115
|
+
bucket.allowed = 0;
|
|
116
|
+
bucket.suppressed = 0;
|
|
117
|
+
bucket.summaryStartTime = now;
|
|
118
|
+
bucket.firstRecord = record;
|
|
119
|
+
}
|
|
120
|
+
pushAcceptedAt(bucket, now);
|
|
121
|
+
bucket.allowed++;
|
|
122
|
+
} else bucket.suppressed++;
|
|
123
|
+
bucket.lastRecord = record;
|
|
124
|
+
bucket.lastTime = now;
|
|
125
|
+
return allowed;
|
|
126
|
+
}
|
|
127
|
+
function pruneExpiredAcceptedAt(bucket, now) {
|
|
128
|
+
let acceptedAtCount = 0;
|
|
129
|
+
for (let i = 0; i < bucket.acceptedAt.length; i++) {
|
|
130
|
+
const acceptedAt = bucket.acceptedAt[i];
|
|
131
|
+
if (acceptedAt <= now && now - acceptedAt < windowMs) {
|
|
132
|
+
bucket.acceptedAt[acceptedAtCount] = acceptedAt;
|
|
133
|
+
acceptedAtCount++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
bucket.acceptedAt.length = acceptedAtCount;
|
|
137
|
+
}
|
|
138
|
+
function pushAcceptedAt(bucket, acceptedAt) {
|
|
139
|
+
bucket.acceptedAt.push(acceptedAt);
|
|
140
|
+
}
|
|
141
|
+
function evictKeysIfNeeded(now) {
|
|
142
|
+
if (maxKeys == null) return;
|
|
143
|
+
while (buckets.size >= maxKeys) {
|
|
144
|
+
const keyToEvict = buckets.keys().next().value;
|
|
145
|
+
if (keyToEvict === void 0) return;
|
|
146
|
+
const bucket = buckets.get(keyToEvict);
|
|
147
|
+
if (bucket != null) {
|
|
148
|
+
const endTime = timeSource === "record" ? bucket.lastTime : Math.max(now, bucket.lastTime);
|
|
149
|
+
emitSummary(keyToEvict, bucket, "eviction", endTime);
|
|
150
|
+
buckets.delete(keyToEvict);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function emitSummary(key, bucket, reason, endTime) {
|
|
155
|
+
const summaryOptions = options.summary;
|
|
156
|
+
if (summaryOptions == null || bucket.suppressed < 1) return;
|
|
157
|
+
const summary = {
|
|
158
|
+
key,
|
|
159
|
+
suppressed: bucket.suppressed,
|
|
160
|
+
allowed: bucket.allowed,
|
|
161
|
+
reason,
|
|
162
|
+
startTime: bucket.summaryStartTime,
|
|
163
|
+
endTime,
|
|
164
|
+
firstRecord: bucket.firstRecord,
|
|
165
|
+
lastRecord: bucket.lastRecord
|
|
166
|
+
};
|
|
167
|
+
const level = typeof summaryOptions.level === "function" ? summaryOptions.level(summary) : summaryOptions.level ?? "warning";
|
|
168
|
+
const message = typeof summaryOptions.message === "function" ? summaryOptions.message(summary) : summaryOptions.message ?? "Last log message was suppressed {suppressed} times.";
|
|
169
|
+
const log = summaryOptions.logger[level];
|
|
170
|
+
if (typeof log !== "function") return;
|
|
171
|
+
const properties = {
|
|
172
|
+
key: summary.key,
|
|
173
|
+
suppressed: summary.suppressed,
|
|
174
|
+
allowed: summary.allowed,
|
|
175
|
+
reason: summary.reason,
|
|
176
|
+
startTime: summary.startTime,
|
|
177
|
+
endTime: summary.endTime,
|
|
178
|
+
firstRecord: summary.firstRecord,
|
|
179
|
+
lastRecord: summary.lastRecord,
|
|
180
|
+
[summaryRecord]: true
|
|
181
|
+
};
|
|
182
|
+
emittingSummary = true;
|
|
183
|
+
try {
|
|
184
|
+
log.call(summaryOptions.logger, message, properties);
|
|
185
|
+
} finally {
|
|
186
|
+
emittingSummary = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function isSummaryRecord(record) {
|
|
190
|
+
const properties = record.properties;
|
|
191
|
+
return properties != null && typeof properties === "object" && properties[summaryRecord] === true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function validatePositiveInteger(name, value) {
|
|
195
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError(`${name} must be a positive integer.`);
|
|
196
|
+
}
|
|
197
|
+
function validatePositiveNumber(name, value) {
|
|
198
|
+
if (!Number.isFinite(value) || value <= 0) throw new RangeError(`${name} must be a positive number.`);
|
|
199
|
+
}
|
|
200
|
+
function getDefaultThrottlingKey(record) {
|
|
201
|
+
const categoryKey = encodeKeyParts(record.category);
|
|
202
|
+
const rawMessage = getRawMessageTemplate(record.rawMessage);
|
|
203
|
+
const rawMessageKey = typeof rawMessage === "string" ? `s${encodeKeyPart(rawMessage)}` : `t${encodeKeyParts(rawMessage)}`;
|
|
204
|
+
return `c${categoryKey}l${encodeKeyPart(record.level)}r${rawMessageKey}`;
|
|
205
|
+
}
|
|
206
|
+
function getRawMessageTemplate(rawMessage) {
|
|
207
|
+
if (typeof rawMessage === "string") return rawMessage;
|
|
208
|
+
for (const part of rawMessage) if (typeof part !== "string") return rawMessage.raw;
|
|
209
|
+
return rawMessage;
|
|
210
|
+
}
|
|
211
|
+
function encodeKeyParts(parts) {
|
|
212
|
+
let key = `${parts.length}:`;
|
|
213
|
+
for (const part of parts) key += encodeKeyPart(part);
|
|
214
|
+
return key;
|
|
215
|
+
}
|
|
216
|
+
function encodeKeyPart(part) {
|
|
217
|
+
return `${part.length}:${part}`;
|
|
218
|
+
}
|
|
29
219
|
|
|
30
220
|
//#endregion
|
|
31
|
-
export { getLevelFilter, toFilter };
|
|
221
|
+
export { getLevelFilter, getThrottlingFilter, toFilter };
|
|
32
222
|
//# sourceMappingURL=filter.js.map
|
package/dist/filter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.js","names":["filter: FilterLike","level: LogLevel | null","record: LogRecord"],"sources":["../src/filter.ts"],"sourcesContent":["import type { LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A filter is a function that accepts a log record and returns `true` if the\n * record should be passed to the sink.\n *\n * @param record The log record to filter.\n * @returns `true` if the record should be passed to the sink.\n */\nexport type Filter = (record: LogRecord) => boolean;\n\n/**\n * A filter-like value is either a {@link Filter} or a {@link LogLevel}.\n * `null` is also allowed to represent a filter that rejects all records.\n */\nexport type FilterLike = Filter | LogLevel | null;\n\n/**\n * Converts a {@link FilterLike} value to an actual {@link Filter}.\n *\n * @param filter The filter-like value to convert.\n * @returns The actual filter.\n */\nexport function toFilter(filter: FilterLike): Filter {\n if (typeof filter === \"function\") return filter;\n return getLevelFilter(filter);\n}\n\n/**\n * Returns a filter that accepts log records with the specified level.\n *\n * @param level The level to filter by. If `null`, the filter will reject all\n * records.\n * @returns The filter.\n */\nexport function getLevelFilter(level: LogLevel | null): Filter {\n if (level == null) return () => false;\n if (level === \"fatal\") {\n return (record: LogRecord) => record.level === \"fatal\";\n } else if (level === \"error\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" || record.level === \"error\";\n } else if (level === \"warning\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\";\n } else if (level === \"info\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\" ||\n record.level === \"info\";\n } else if (level === \"debug\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\" ||\n record.level === \"info\" ||\n record.level === \"debug\";\n } else if (level === \"trace\") return () => true;\n throw new TypeError(`Invalid log level: ${level}.`);\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,SAASA,QAA4B;AACnD,YAAW,WAAW,WAAY,QAAO;AACzC,QAAO,eAAe,OAAO;AAC9B;;;;;;;;AASD,SAAgB,eAAeC,OAAgC;AAC7D,KAAI,SAAS,KAAM,QAAO,MAAM;AAChC,KAAI,UAAU,QACZ,QAAO,CAACC,WAAsB,OAAO,UAAU;UACtC,UAAU,QACnB,QAAO,CAACA,WACN,OAAO,UAAU,WAAW,OAAO,UAAU;UACtC,UAAU,UACnB,QAAO,CAACA,WACN,OAAO,UAAU,WACjB,OAAO,UAAU,WACjB,OAAO,UAAU;UACV,UAAU,OACnB,QAAO,CAACA,WACN,OAAO,UAAU,WACjB,OAAO,UAAU,WACjB,OAAO,UAAU,aACjB,OAAO,UAAU;UACV,UAAU,QACnB,QAAO,CAACA,WACN,OAAO,UAAU,WACjB,OAAO,UAAU,WACjB,OAAO,UAAU,aACjB,OAAO,UAAU,UACjB,OAAO,UAAU;UACV,UAAU,QAAS,QAAO,MAAM;AAC3C,OAAM,IAAI,WAAW,qBAAqB,MAAM;AACjD"}
|
|
1
|
+
{"version":3,"file":"filter.js","names":["filter: FilterLike","level: LogLevel | null","record: LogRecord","options: ThrottlingFilterOptions","key: string","bucket: ThrottlingBucket","now: number","acceptedAt: number","reason: ThrottlingFilterSummary[\"reason\"]","endTime: number","summary: ThrottlingFilterSummary","name: string","value: number","rawMessage: string | TemplateStringsArray","parts: readonly string[]","part: string"],"sources":["../src/filter.ts"],"sourcesContent":["import type { LogLevel } from \"./level.ts\";\nimport type { LogRecord } from \"./record.ts\";\n\n/**\n * A filter is a function that accepts a log record and returns `true` if the\n * record should be passed to the sink.\n *\n * @param record The log record to filter.\n * @returns `true` if the record should be passed to the sink.\n */\nexport type Filter = (record: LogRecord) => boolean;\n\n/**\n * Summary information emitted by {@link getThrottlingFilter} when suppressed\n * records are reported.\n *\n * @since 2.1.0\n */\nexport interface ThrottlingFilterSummary {\n /**\n * The throttling key whose records were suppressed.\n */\n readonly key: string;\n\n /**\n * The number of records suppressed since the previous summary for the key.\n */\n readonly suppressed: number;\n\n /**\n * The number of records allowed during the same summary period.\n */\n readonly allowed: number;\n\n /**\n * Why the summary was emitted.\n */\n readonly reason: \"window\" | \"eviction\" | \"dispose\";\n\n /**\n * The time at which the summary period started, in milliseconds.\n */\n readonly startTime: number;\n\n /**\n * The time at which the summary period ended, in milliseconds.\n */\n readonly endTime: number;\n\n /**\n * The first record observed in the summary period.\n */\n readonly firstRecord: LogRecord;\n\n /**\n * The most recent record observed in the summary period.\n */\n readonly lastRecord: LogRecord;\n}\n\n/**\n * A logger-like object used by {@link getThrottlingFilter} to emit summaries.\n *\n * A regular {@link Logger} returned by `getLogger()` satisfies this interface.\n *\n * @since 2.1.0\n */\nexport type ThrottlingSummaryLogger = {\n readonly [Level in LogLevel]?: (\n message: string,\n properties: Record<string, unknown>,\n ) => void;\n};\n\n/**\n * Summary logging options for {@link getThrottlingFilter}.\n *\n * @since 2.1.0\n */\nexport interface ThrottlingFilterSummaryOptions {\n /**\n * The logger used to emit summary records.\n */\n readonly logger: ThrottlingSummaryLogger;\n\n /**\n * The summary log level.\n * @default `\"warning\"`\n */\n readonly level?:\n | LogLevel\n | ((summary: ThrottlingFilterSummary) => LogLevel);\n\n /**\n * The summary message.\n * @default `\"Last log message was suppressed {suppressed} times.\"`\n */\n readonly message?:\n | string\n | ((summary: ThrottlingFilterSummary) => string);\n}\n\n/**\n * Options for {@link getThrottlingFilter}.\n *\n * @since 2.1.0\n */\nexport interface ThrottlingFilterOptions {\n /**\n * Number of records allowed for each key in the configured window.\n */\n readonly limit: number;\n\n /**\n * Window size in milliseconds.\n */\n readonly windowMs: number;\n\n /**\n * Window algorithm.\n *\n * - `\"fixed\"` starts a window when the first record for a key arrives.\n * - `\"sliding\"` counts records accepted during the previous `windowMs`.\n *\n * @default `\"fixed\"`\n */\n readonly mode?: \"fixed\" | \"sliding\";\n\n /**\n * Source of time for window calculations.\n *\n * - `\"clock\"` uses {@link ThrottlingFilterOptions.clock}.\n * - `\"record\"` uses {@link LogRecord.timestamp}.\n *\n * @default `\"clock\"`\n */\n readonly timeSource?: \"clock\" | \"record\";\n\n /**\n * Clock used when {@link timeSource} is `\"clock\"`.\n * @default `Date.now`\n */\n readonly clock?: () => number;\n\n /**\n * Derives a throttling key from a log record. By default, records are keyed\n * by category, level, and raw message template.\n */\n readonly key?: (record: LogRecord) => string;\n\n /**\n * Maximum number of keys tracked by the filter. When the limit is reached,\n * the least recently used key is evicted. Set to `null` to disable the cap.\n *\n * @default `1000`\n */\n readonly maxKeys?: number | null;\n\n /**\n * Summary logging options for suppressed records.\n */\n readonly summary?: ThrottlingFilterSummaryOptions;\n}\n\n/**\n * A filter-like value is either a {@link Filter} or a {@link LogLevel}.\n * `null` is also allowed to represent a filter that rejects all records.\n */\nexport type FilterLike = Filter | LogLevel | null;\n\n/**\n * Converts a {@link FilterLike} value to an actual {@link Filter}.\n *\n * @param filter The filter-like value to convert.\n * @returns The actual filter.\n */\nexport function toFilter(filter: FilterLike): Filter {\n if (typeof filter === \"function\") return filter;\n return getLevelFilter(filter);\n}\n\n/**\n * Returns a filter that accepts log records with the specified level.\n *\n * @param level The level to filter by. If `null`, the filter will reject all\n * records.\n * @returns The filter.\n */\nexport function getLevelFilter(level: LogLevel | null): Filter {\n if (level == null) return () => false;\n if (level === \"fatal\") {\n return (record: LogRecord) => record.level === \"fatal\";\n } else if (level === \"error\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" || record.level === \"error\";\n } else if (level === \"warning\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\";\n } else if (level === \"info\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\" ||\n record.level === \"info\";\n } else if (level === \"debug\") {\n return (record: LogRecord) =>\n record.level === \"fatal\" ||\n record.level === \"error\" ||\n record.level === \"warning\" ||\n record.level === \"info\" ||\n record.level === \"debug\";\n } else if (level === \"trace\") return () => true;\n throw new TypeError(`Invalid log level: ${level}.`);\n}\n\ninterface ThrottlingBucket {\n windowStart: number;\n summaryStartTime: number;\n readonly acceptedAt: number[];\n allowed: number;\n suppressed: number;\n firstRecord: LogRecord;\n lastRecord: LogRecord;\n lastTime: number;\n}\n\n/**\n * Returns a stateful filter that rate-limits repeated log records.\n *\n * The default key treats records with the same category, level, and raw\n * message template as identical, ignoring substituted values. If summary\n * logging is enabled, the filter logs summaries as a side effect when\n * suppression ends, when a suppressed key is evicted, or when the filter is\n * disposed.\n *\n * @param options Throttling options.\n * @returns A throttling filter.\n * @since 2.1.0\n */\nexport function getThrottlingFilter(\n options: ThrottlingFilterOptions,\n): Filter & Disposable {\n validatePositiveInteger(\"limit\", options.limit);\n validatePositiveNumber(\"windowMs\", options.windowMs);\n if (options.maxKeys != null) {\n validatePositiveInteger(\"maxKeys\", options.maxKeys);\n }\n\n const limit = options.limit;\n const windowMs = options.windowMs;\n const mode = options.mode ?? \"fixed\";\n const timeSource = options.timeSource ?? \"clock\";\n const clock = options.clock ?? Date.now;\n const getKey = options.key ?? getDefaultThrottlingKey;\n const maxKeys = options.maxKeys === undefined ? 1000 : options.maxKeys;\n const buckets = new Map<string, ThrottlingBucket>();\n const summaryRecord = Symbol.for(\"LogTape.throttlingSummaryRecord\");\n let emittingSummary = false;\n\n if (mode !== \"fixed\" && mode !== \"sliding\") {\n throw new TypeError(`Invalid throttling mode: ${String(mode)}.`);\n }\n if (timeSource !== \"clock\" && timeSource !== \"record\") {\n throw new TypeError(\n `Invalid throttling timeSource: ${String(timeSource)}.`,\n );\n }\n\n const filter = ((record: LogRecord): boolean => {\n if (emittingSummary && isSummaryRecord(record)) {\n return true;\n }\n\n const key = getKey(record);\n let bucket = buckets.get(key);\n const observedTime = timeSource === \"record\" ? record.timestamp : clock();\n const now = bucket == null || timeSource === \"record\"\n ? observedTime\n : Math.max(observedTime, bucket.lastTime);\n\n if (bucket == null) {\n evictKeysIfNeeded(now);\n bucket = {\n windowStart: now,\n summaryStartTime: now,\n acceptedAt: [],\n allowed: 0,\n suppressed: 0,\n firstRecord: record,\n lastRecord: record,\n lastTime: now,\n };\n buckets.set(key, bucket);\n } else if (maxKeys != null) {\n buckets.delete(key);\n buckets.set(key, bucket);\n }\n\n if (mode === \"fixed\") {\n return filterFixedWindow(key, bucket, record, now);\n }\n return filterSlidingWindow(key, bucket, record, now);\n }) as Filter & Disposable;\n\n filter[Symbol.dispose] = () => {\n for (const [key, bucket] of buckets) {\n const endTime = timeSource === \"record\"\n ? bucket.lastTime\n : Math.max(clock(), bucket.lastTime);\n emitSummary(key, bucket, \"dispose\", endTime);\n }\n buckets.clear();\n };\n\n return filter;\n\n function filterFixedWindow(\n key: string,\n bucket: ThrottlingBucket,\n record: LogRecord,\n now: number,\n ): boolean {\n if (now - bucket.windowStart >= windowMs) {\n emitSummary(key, bucket, \"window\", now);\n bucket.windowStart = now;\n bucket.summaryStartTime = now;\n bucket.acceptedAt.length = 0;\n bucket.allowed = 0;\n bucket.suppressed = 0;\n bucket.firstRecord = record;\n }\n\n const allowed = bucket.allowed < limit;\n if (allowed) {\n bucket.allowed++;\n } else {\n bucket.suppressed++;\n }\n\n bucket.lastRecord = record;\n bucket.lastTime = now;\n return allowed;\n }\n\n function filterSlidingWindow(\n key: string,\n bucket: ThrottlingBucket,\n record: LogRecord,\n now: number,\n ): boolean {\n pruneExpiredAcceptedAt(bucket, now);\n\n const allowed = bucket.acceptedAt.length < limit;\n if (allowed) {\n if (bucket.suppressed > 0) {\n emitSummary(key, bucket, \"window\", now);\n bucket.allowed = 0;\n bucket.suppressed = 0;\n bucket.summaryStartTime = now;\n bucket.firstRecord = record;\n }\n pushAcceptedAt(bucket, now);\n bucket.allowed++;\n } else {\n bucket.suppressed++;\n }\n\n bucket.lastRecord = record;\n bucket.lastTime = now;\n return allowed;\n }\n\n function pruneExpiredAcceptedAt(\n bucket: ThrottlingBucket,\n now: number,\n ): void {\n let acceptedAtCount = 0;\n for (let i = 0; i < bucket.acceptedAt.length; i++) {\n const acceptedAt: number = bucket.acceptedAt[i];\n if (acceptedAt <= now && now - acceptedAt < windowMs) {\n bucket.acceptedAt[acceptedAtCount] = acceptedAt;\n acceptedAtCount++;\n }\n }\n bucket.acceptedAt.length = acceptedAtCount;\n }\n\n function pushAcceptedAt(bucket: ThrottlingBucket, acceptedAt: number): void {\n bucket.acceptedAt.push(acceptedAt);\n }\n\n function evictKeysIfNeeded(now: number): void {\n if (maxKeys == null) return;\n while (buckets.size >= maxKeys) {\n const keyToEvict = buckets.keys().next().value;\n if (keyToEvict === undefined) return;\n const bucket = buckets.get(keyToEvict);\n if (bucket != null) {\n const endTime = timeSource === \"record\"\n ? bucket.lastTime\n : Math.max(now, bucket.lastTime);\n emitSummary(keyToEvict, bucket, \"eviction\", endTime);\n buckets.delete(keyToEvict);\n }\n }\n }\n\n function emitSummary(\n key: string,\n bucket: ThrottlingBucket,\n reason: ThrottlingFilterSummary[\"reason\"],\n endTime: number,\n ): void {\n const summaryOptions = options.summary;\n if (summaryOptions == null || bucket.suppressed < 1) return;\n\n const summary: ThrottlingFilterSummary = {\n key,\n suppressed: bucket.suppressed,\n allowed: bucket.allowed,\n reason,\n startTime: bucket.summaryStartTime,\n endTime,\n firstRecord: bucket.firstRecord,\n lastRecord: bucket.lastRecord,\n };\n const level = typeof summaryOptions.level === \"function\"\n ? summaryOptions.level(summary)\n : summaryOptions.level ?? \"warning\";\n const message = typeof summaryOptions.message === \"function\"\n ? summaryOptions.message(summary)\n : summaryOptions.message ??\n \"Last log message was suppressed {suppressed} times.\";\n const log = summaryOptions.logger[level];\n if (typeof log !== \"function\") return;\n const properties = {\n key: summary.key,\n suppressed: summary.suppressed,\n allowed: summary.allowed,\n reason: summary.reason,\n startTime: summary.startTime,\n endTime: summary.endTime,\n firstRecord: summary.firstRecord,\n lastRecord: summary.lastRecord,\n [summaryRecord]: true,\n } as Record<string, unknown> & Record<typeof summaryRecord, true>;\n\n emittingSummary = true;\n try {\n log.call(summaryOptions.logger, message, properties);\n } finally {\n emittingSummary = false;\n }\n }\n\n function isSummaryRecord(record: LogRecord): boolean {\n const properties = record.properties;\n return properties != null &&\n typeof properties === \"object\" &&\n (properties as Record<typeof summaryRecord, unknown>)[summaryRecord] ===\n true;\n }\n}\n\nfunction validatePositiveInteger(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 1) {\n throw new RangeError(`${name} must be a positive integer.`);\n }\n}\n\nfunction validatePositiveNumber(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`${name} must be a positive number.`);\n }\n}\n\nfunction getDefaultThrottlingKey(record: LogRecord): string {\n const categoryKey = encodeKeyParts(record.category);\n const rawMessage = getRawMessageTemplate(record.rawMessage);\n const rawMessageKey = typeof rawMessage === \"string\"\n ? `s${encodeKeyPart(rawMessage)}`\n : `t${encodeKeyParts(rawMessage)}`;\n return `c${categoryKey}l${encodeKeyPart(record.level)}r${rawMessageKey}`;\n}\n\nfunction getRawMessageTemplate(\n rawMessage: string | TemplateStringsArray,\n): string | readonly string[] {\n if (typeof rawMessage === \"string\") return rawMessage;\n for (const part of rawMessage) {\n if (typeof part !== \"string\") return rawMessage.raw;\n }\n return rawMessage;\n}\n\nfunction encodeKeyParts(parts: readonly string[]): string {\n let key = `${parts.length}:`;\n for (const part of parts) key += encodeKeyPart(part);\n return key;\n}\n\nfunction encodeKeyPart(part: string): string {\n return `${part.length}:${part}`;\n}\n"],"mappings":"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"}
|