@loglayer/mixin-wide-events 1.2.1 → 1.4.0
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/index.cjs +46 -9
- package/dist/index.d.cts +31 -0
- package/dist/index.d.mts +31 -0
- package/dist/index.mjs +46 -9
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -94,7 +94,8 @@ function createWideEventMixin(options) {
|
|
|
94
94
|
rate: options.sampling.rate,
|
|
95
95
|
perLevel: snapshotPerLevel,
|
|
96
96
|
emitLevel: options.sampling.emitLevel,
|
|
97
|
-
shouldEmit: options.sampling.shouldEmit
|
|
97
|
+
shouldEmit: options.sampling.shouldEmit,
|
|
98
|
+
forceKeep: options.sampling.forceKeep
|
|
98
99
|
} : void 0;
|
|
99
100
|
function defaultErrorSerializer(err) {
|
|
100
101
|
if (err instanceof Error) return {
|
|
@@ -189,23 +190,59 @@ function createWideEventMixin(options) {
|
|
|
189
190
|
return self;
|
|
190
191
|
}
|
|
191
192
|
/**
|
|
193
|
+
* Logs a thrown sampling callback via console.error, gated on `consoleDebug`.
|
|
194
|
+
* Matches loglayer's emit-path error convention (see LogLayer.ts:1457).
|
|
195
|
+
*/
|
|
196
|
+
function logCallbackError(callbackName, err, self) {
|
|
197
|
+
if (self.getConfig?.()?.consoleDebug) console.error(`[LogLayer] wide-events ${callbackName} callback threw; falling back:`, err);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Single sampling decision for an emit. Only called when a `forceKeep` or
|
|
201
|
+
* `shouldEmit` callback is configured (the no-callback case is handled by the
|
|
202
|
+
* pre-build fast-path in emitWideEventImpl).
|
|
203
|
+
*
|
|
204
|
+
* - `forceKeep` runs first (OR logic): true → keep; throws → fail-safe (log +
|
|
205
|
+
* fall through); false → fall through.
|
|
206
|
+
* - Standard logic: rate AND `shouldEmit` (shouldEmit throws → fail-open, log
|
|
207
|
+
* + keep). Without `shouldEmit`, rate alone decides.
|
|
208
|
+
*
|
|
209
|
+
* Invokes `runRateSampling` at most once.
|
|
210
|
+
*/
|
|
211
|
+
function evaluateSampling(wideData, level, self) {
|
|
212
|
+
if (samplingConfig?.forceKeep) try {
|
|
213
|
+
if (samplingConfig.forceKeep({
|
|
214
|
+
wideData,
|
|
215
|
+
level
|
|
216
|
+
})) return true;
|
|
217
|
+
} catch (err) {
|
|
218
|
+
logCallbackError("forceKeep", err, self);
|
|
219
|
+
}
|
|
220
|
+
if (samplingConfig?.shouldEmit) try {
|
|
221
|
+
const callbackOk = samplingConfig.shouldEmit({
|
|
222
|
+
wideData,
|
|
223
|
+
level
|
|
224
|
+
});
|
|
225
|
+
return runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) && callbackOk;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
logCallbackError("shouldEmit", err, self);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return runRateSampling(level, samplingConfig?.strategy, samplingConfig?.rate, samplingConfig?.perLevel);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
192
233
|
* Implementation for emitWideEvent
|
|
193
234
|
*/
|
|
194
235
|
function emitWideEventImpl(config, self) {
|
|
195
236
|
const defaultLevel = samplingConfig?.emitLevel ?? "info";
|
|
196
237
|
const level = config.level ?? defaultLevel;
|
|
197
|
-
if (samplingConfig && !samplingConfig.shouldEmit && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
238
|
+
if (samplingConfig && !samplingConfig.shouldEmit && !samplingConfig.forceKeep && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
198
239
|
const store = asyncContext.getStore();
|
|
199
240
|
const wideEventData = {};
|
|
200
241
|
if (includeContext && store?._llContext) Object.assign(wideEventData, store._llContext);
|
|
201
242
|
if (store?._llWideEvents) Object.assign(wideEventData, store._llWideEvents);
|
|
202
|
-
if (samplingConfig
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
level
|
|
206
|
-
});
|
|
207
|
-
if (!runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) || !callbackOk) return;
|
|
208
|
-
} catch {}
|
|
243
|
+
if (samplingConfig && (samplingConfig.forceKeep || samplingConfig.shouldEmit)) {
|
|
244
|
+
if (!evaluateSampling(wideEventData, level, self)) return;
|
|
245
|
+
}
|
|
209
246
|
const metadataToEmit = wideEventField ? { [wideEventField]: wideEventData } : wideEventData;
|
|
210
247
|
self.raw({
|
|
211
248
|
logLevel: level,
|
package/dist/index.d.cts
CHANGED
|
@@ -107,6 +107,37 @@ interface WideEventSamplingConfig {
|
|
|
107
107
|
* ```
|
|
108
108
|
*/
|
|
109
109
|
shouldEmit?: (params: WideEventSamplingParams) => boolean;
|
|
110
|
+
/**
|
|
111
|
+
* A keep-only override evaluated **before** the rate/`shouldEmit` checks.
|
|
112
|
+
* When it returns `true`, the event is emitted immediately, bypassing the
|
|
113
|
+
* rate check and `shouldEmit`. When it returns `false`, standard
|
|
114
|
+
* rate/`shouldEmit` logic applies. Receives the same params as `shouldEmit`,
|
|
115
|
+
* so any external signal (e.g. an HTTP header) must first be written into the
|
|
116
|
+
* wide event via `withWideEvents()`.
|
|
117
|
+
*
|
|
118
|
+
* `forceKeep` can only rescue events — it never drops an event that standard
|
|
119
|
+
* logic would keep (`error`/`fatal` stay exempt).
|
|
120
|
+
*
|
|
121
|
+
* Treat `wideData` as read-only: it is passed by reference and mutating it
|
|
122
|
+
* changes what gets emitted.
|
|
123
|
+
*
|
|
124
|
+
* If it throws, it fails **safe**: the throw is swallowed and evaluation
|
|
125
|
+
* falls through to the standard rate/`shouldEmit` logic (unlike `shouldEmit`,
|
|
126
|
+
* which fails open). A swallowed throw is logged via `console.error` when
|
|
127
|
+
* `consoleDebug` is enabled.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* {
|
|
132
|
+
* strategy: "per_level",
|
|
133
|
+
* perLevel: { info: 0.01 },
|
|
134
|
+
* forceKeep: ({ wideData }) =>
|
|
135
|
+
* wideData?.debug === true ||
|
|
136
|
+
* (wideData?.deps?.someService?.failures ?? 0) > 0,
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
forceKeep?: (params: WideEventSamplingParams) => boolean;
|
|
110
141
|
/**
|
|
111
142
|
* Override the default log level when no explicit `level` is passed to
|
|
112
143
|
* `emitWideEvent()`. When set, the resolved level uses this value instead
|
package/dist/index.d.mts
CHANGED
|
@@ -107,6 +107,37 @@ interface WideEventSamplingConfig {
|
|
|
107
107
|
* ```
|
|
108
108
|
*/
|
|
109
109
|
shouldEmit?: (params: WideEventSamplingParams) => boolean;
|
|
110
|
+
/**
|
|
111
|
+
* A keep-only override evaluated **before** the rate/`shouldEmit` checks.
|
|
112
|
+
* When it returns `true`, the event is emitted immediately, bypassing the
|
|
113
|
+
* rate check and `shouldEmit`. When it returns `false`, standard
|
|
114
|
+
* rate/`shouldEmit` logic applies. Receives the same params as `shouldEmit`,
|
|
115
|
+
* so any external signal (e.g. an HTTP header) must first be written into the
|
|
116
|
+
* wide event via `withWideEvents()`.
|
|
117
|
+
*
|
|
118
|
+
* `forceKeep` can only rescue events — it never drops an event that standard
|
|
119
|
+
* logic would keep (`error`/`fatal` stay exempt).
|
|
120
|
+
*
|
|
121
|
+
* Treat `wideData` as read-only: it is passed by reference and mutating it
|
|
122
|
+
* changes what gets emitted.
|
|
123
|
+
*
|
|
124
|
+
* If it throws, it fails **safe**: the throw is swallowed and evaluation
|
|
125
|
+
* falls through to the standard rate/`shouldEmit` logic (unlike `shouldEmit`,
|
|
126
|
+
* which fails open). A swallowed throw is logged via `console.error` when
|
|
127
|
+
* `consoleDebug` is enabled.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* {
|
|
132
|
+
* strategy: "per_level",
|
|
133
|
+
* perLevel: { info: 0.01 },
|
|
134
|
+
* forceKeep: ({ wideData }) =>
|
|
135
|
+
* wideData?.debug === true ||
|
|
136
|
+
* (wideData?.deps?.someService?.failures ?? 0) > 0,
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
forceKeep?: (params: WideEventSamplingParams) => boolean;
|
|
110
141
|
/**
|
|
111
142
|
* Override the default log level when no explicit `level` is passed to
|
|
112
143
|
* `emitWideEvent()`. When set, the resolved level uses this value instead
|
package/dist/index.mjs
CHANGED
|
@@ -93,7 +93,8 @@ function createWideEventMixin(options) {
|
|
|
93
93
|
rate: options.sampling.rate,
|
|
94
94
|
perLevel: snapshotPerLevel,
|
|
95
95
|
emitLevel: options.sampling.emitLevel,
|
|
96
|
-
shouldEmit: options.sampling.shouldEmit
|
|
96
|
+
shouldEmit: options.sampling.shouldEmit,
|
|
97
|
+
forceKeep: options.sampling.forceKeep
|
|
97
98
|
} : void 0;
|
|
98
99
|
function defaultErrorSerializer(err) {
|
|
99
100
|
if (err instanceof Error) return {
|
|
@@ -188,23 +189,59 @@ function createWideEventMixin(options) {
|
|
|
188
189
|
return self;
|
|
189
190
|
}
|
|
190
191
|
/**
|
|
192
|
+
* Logs a thrown sampling callback via console.error, gated on `consoleDebug`.
|
|
193
|
+
* Matches loglayer's emit-path error convention (see LogLayer.ts:1457).
|
|
194
|
+
*/
|
|
195
|
+
function logCallbackError(callbackName, err, self) {
|
|
196
|
+
if (self.getConfig?.()?.consoleDebug) console.error(`[LogLayer] wide-events ${callbackName} callback threw; falling back:`, err);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Single sampling decision for an emit. Only called when a `forceKeep` or
|
|
200
|
+
* `shouldEmit` callback is configured (the no-callback case is handled by the
|
|
201
|
+
* pre-build fast-path in emitWideEventImpl).
|
|
202
|
+
*
|
|
203
|
+
* - `forceKeep` runs first (OR logic): true → keep; throws → fail-safe (log +
|
|
204
|
+
* fall through); false → fall through.
|
|
205
|
+
* - Standard logic: rate AND `shouldEmit` (shouldEmit throws → fail-open, log
|
|
206
|
+
* + keep). Without `shouldEmit`, rate alone decides.
|
|
207
|
+
*
|
|
208
|
+
* Invokes `runRateSampling` at most once.
|
|
209
|
+
*/
|
|
210
|
+
function evaluateSampling(wideData, level, self) {
|
|
211
|
+
if (samplingConfig?.forceKeep) try {
|
|
212
|
+
if (samplingConfig.forceKeep({
|
|
213
|
+
wideData,
|
|
214
|
+
level
|
|
215
|
+
})) return true;
|
|
216
|
+
} catch (err) {
|
|
217
|
+
logCallbackError("forceKeep", err, self);
|
|
218
|
+
}
|
|
219
|
+
if (samplingConfig?.shouldEmit) try {
|
|
220
|
+
const callbackOk = samplingConfig.shouldEmit({
|
|
221
|
+
wideData,
|
|
222
|
+
level
|
|
223
|
+
});
|
|
224
|
+
return runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) && callbackOk;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
logCallbackError("shouldEmit", err, self);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
return runRateSampling(level, samplingConfig?.strategy, samplingConfig?.rate, samplingConfig?.perLevel);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
191
232
|
* Implementation for emitWideEvent
|
|
192
233
|
*/
|
|
193
234
|
function emitWideEventImpl(config, self) {
|
|
194
235
|
const defaultLevel = samplingConfig?.emitLevel ?? "info";
|
|
195
236
|
const level = config.level ?? defaultLevel;
|
|
196
|
-
if (samplingConfig && !samplingConfig.shouldEmit && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
237
|
+
if (samplingConfig && !samplingConfig.shouldEmit && !samplingConfig.forceKeep && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
197
238
|
const store = asyncContext.getStore();
|
|
198
239
|
const wideEventData = {};
|
|
199
240
|
if (includeContext && store?._llContext) Object.assign(wideEventData, store._llContext);
|
|
200
241
|
if (store?._llWideEvents) Object.assign(wideEventData, store._llWideEvents);
|
|
201
|
-
if (samplingConfig
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
level
|
|
205
|
-
});
|
|
206
|
-
if (!runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) || !callbackOk) return;
|
|
207
|
-
} catch {}
|
|
242
|
+
if (samplingConfig && (samplingConfig.forceKeep || samplingConfig.shouldEmit)) {
|
|
243
|
+
if (!evaluateSampling(wideEventData, level, self)) return;
|
|
244
|
+
}
|
|
208
245
|
const metadataToEmit = wideEventField ? { [wideEventField]: wideEventData } : wideEventData;
|
|
209
246
|
self.raw({
|
|
210
247
|
logLevel: level,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loglayer/mixin-wide-events",
|
|
3
3
|
"description": "Adds wide event logging functionality to LogLayer for comprehensive, self-contained log entries.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"typescript": "6.0.3",
|
|
39
39
|
"vitest": "4.1.7",
|
|
40
40
|
"@internal/tsconfig": "2.1.0",
|
|
41
|
-
"@loglayer/shared": "4.
|
|
42
|
-
"loglayer": "9.
|
|
41
|
+
"@loglayer/shared": "4.4.0",
|
|
42
|
+
"loglayer": "9.4.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"loglayer": ">=9.0.0"
|