@loglayer/mixin-wide-events 1.0.1 → 1.1.1
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/README.md +15 -5
- package/dist/index.cjs +57 -2
- package/dist/index.d.cts +132 -2
- package/dist/index.d.mts +132 -2
- package/dist/index.mjs +57 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wide-events mixin for LogLayer
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@loglayer/mixin-wide-events)
|
|
4
|
+
[](https://www.npmjs.com/package/@loglayer/mixin-wide-events)
|
|
5
5
|
[](http://www.typescriptlang.org/)
|
|
6
6
|
|
|
7
|
-
Adds wide event logging functionality to LogLayer
|
|
7
|
+
Adds wide event logging functionality to the [LogLayer](https://loglayer.dev) logging library, enabling comprehensive, self-contained log entries that capture an entire operation's context using Node.js `AsyncLocalStorage`.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The mixin provides methods to accumulate data across async boundaries and emit it as a single log entry, along with built-in sampling support.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install loglayer @loglayer/mixin-wide-events
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
For more details, visit [https://loglayer.dev/mixins/wide-events](https://loglayer.dev/mixins/wide-events).
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,45 @@ let LogLayerMixinAugmentType = /* @__PURE__ */ function(LogLayerMixinAugmentType
|
|
|
37
37
|
//#endregion
|
|
38
38
|
//#region src/mixin.ts
|
|
39
39
|
/**
|
|
40
|
+
* Log levels that default to rate=1 (error/fatal) — can be overridden via `perLevel` or callback.
|
|
41
|
+
*/
|
|
42
|
+
const EXEMPT_LEVELS = new Set(["error", "fatal"]);
|
|
43
|
+
/**
|
|
44
|
+
* Normalize a boolean or numeric rate to a number in [0, 1].
|
|
45
|
+
* `true` becomes 1, `false` becomes 0, numbers are clamped to [0, 1].
|
|
46
|
+
* `NaN`, `Infinity`, and `-Infinity` are treated as 0 (drop all).
|
|
47
|
+
*/
|
|
48
|
+
function toRate(value) {
|
|
49
|
+
if (value === void 0) return 1;
|
|
50
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
51
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
|
52
|
+
return Math.max(0, Math.min(1, value));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Run rate-based sampling (default or per_level strategy).
|
|
56
|
+
* Returns true if the rate check passes, false if dropped.
|
|
57
|
+
* Returns true for error/fatal by default, unless their rate is explicitly set in `perLevel`.
|
|
58
|
+
*/
|
|
59
|
+
function runRateSampling(level, strategy, rate, perLevel) {
|
|
60
|
+
if (EXEMPT_LEVELS.has(level) && perLevel?.[level] === void 0) return true;
|
|
61
|
+
if (strategy === "per_level" && perLevel) {
|
|
62
|
+
const perRate = toRate(perLevel[level]);
|
|
63
|
+
if (perLevel[level] === void 0) {
|
|
64
|
+
const r = toRate(rate);
|
|
65
|
+
if (r === 1) return true;
|
|
66
|
+
if (r === 0) return false;
|
|
67
|
+
return Math.random() < r;
|
|
68
|
+
}
|
|
69
|
+
if (perRate === 1) return true;
|
|
70
|
+
if (perRate === 0) return false;
|
|
71
|
+
return Math.random() < perRate;
|
|
72
|
+
}
|
|
73
|
+
const r = toRate(rate);
|
|
74
|
+
if (r === 1) return true;
|
|
75
|
+
if (r === 0) return false;
|
|
76
|
+
return Math.random() < r;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
40
79
|
* Creates a LogLayer mixin that adds wide event logging functionality.
|
|
41
80
|
*
|
|
42
81
|
* Wide events are comprehensive, self-contained log entries that capture an entire
|
|
@@ -48,7 +87,6 @@ let LogLayerMixinAugmentType = /* @__PURE__ */ function(LogLayerMixinAugmentType
|
|
|
48
87
|
* wide event data across async boundaries. For Node.js, use `new AsyncLocalStorage()`
|
|
49
88
|
* from `async_hooks`. For browser environments, provide your own compatible implementation.
|
|
50
89
|
* @param options.includeContext - Whether to include withContext() data in emitted wide events (default: true)
|
|
51
|
-
|
|
52
90
|
*
|
|
53
91
|
* @returns A LogLayer mixin registration object
|
|
54
92
|
*
|
|
@@ -85,6 +123,14 @@ function createWideEventMixin(options) {
|
|
|
85
123
|
const wideEventField = options.wideEventField;
|
|
86
124
|
const errorsAsArray = options.errorsAsArray ?? false;
|
|
87
125
|
const errorField = options.errorField ?? (errorsAsArray ? "errors" : "error");
|
|
126
|
+
const snapshotPerLevel = options.sampling?.perLevel ? { ...options.sampling.perLevel } : void 0;
|
|
127
|
+
const samplingConfig = options.sampling ? {
|
|
128
|
+
strategy: options.sampling.strategy ?? "default",
|
|
129
|
+
rate: options.sampling.rate,
|
|
130
|
+
perLevel: snapshotPerLevel,
|
|
131
|
+
emitLevel: options.sampling.emitLevel,
|
|
132
|
+
shouldEmit: options.sampling.shouldEmit
|
|
133
|
+
} : void 0;
|
|
88
134
|
function defaultErrorSerializer(err) {
|
|
89
135
|
if (err instanceof Error) return {
|
|
90
136
|
name: err.name,
|
|
@@ -181,11 +227,20 @@ function createWideEventMixin(options) {
|
|
|
181
227
|
* Implementation for emitWideEvent
|
|
182
228
|
*/
|
|
183
229
|
function emitWideEventImpl(config, self) {
|
|
230
|
+
const defaultLevel = samplingConfig?.emitLevel ?? "info";
|
|
231
|
+
const level = config.level ?? defaultLevel;
|
|
232
|
+
if (samplingConfig && !samplingConfig.shouldEmit && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
184
233
|
const store = asyncContext.getStore();
|
|
185
234
|
const wideEventData = {};
|
|
186
235
|
if (includeContext && store?._llContext) Object.assign(wideEventData, store._llContext);
|
|
187
236
|
if (store?._llWideEvents) Object.assign(wideEventData, store._llWideEvents);
|
|
188
|
-
|
|
237
|
+
if (samplingConfig?.shouldEmit) try {
|
|
238
|
+
const callbackOk = samplingConfig.shouldEmit({
|
|
239
|
+
wideData: wideEventData,
|
|
240
|
+
level
|
|
241
|
+
});
|
|
242
|
+
if (!runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) || !callbackOk) return;
|
|
243
|
+
} catch {}
|
|
189
244
|
const metadataToEmit = wideEventField ? { [wideEventField]: wideEventData } : wideEventData;
|
|
190
245
|
self.withMetadata(metadataToEmit)[level](config.message);
|
|
191
246
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -2060,6 +2060,121 @@ declare class LogBuilder implements ILogBuilder<LogBuilder, boolean> {
|
|
|
2060
2060
|
*/
|
|
2061
2061
|
//#endregion
|
|
2062
2062
|
//#region src/types.d.ts
|
|
2063
|
+
/**
|
|
2064
|
+
* Parameters passed to a custom `shouldEmit` sampling callback.
|
|
2065
|
+
* Provides the accumulated wide event data and the log level so the
|
|
2066
|
+
* callback can make an informed keep/drop decision.
|
|
2067
|
+
*/
|
|
2068
|
+
interface WideEventSamplingParams {
|
|
2069
|
+
/**
|
|
2070
|
+
* The accumulated wide event data (wide events + optional context).
|
|
2071
|
+
* Contains the merged data that would be emitted if kept.
|
|
2072
|
+
*/
|
|
2073
|
+
wideData: Record<string, any>;
|
|
2074
|
+
/**
|
|
2075
|
+
* The log level that would be used for the emission.
|
|
2076
|
+
*/
|
|
2077
|
+
level: LogLevelType;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Sampling strategy for wide events.
|
|
2081
|
+
*
|
|
2082
|
+
* - `"default"` — a single `rate` applies to all non-error/fatal levels.
|
|
2083
|
+
* - `"per_level"` — per-level rates keyed by LogLevelType; levels not in the
|
|
2084
|
+
* map use `rate` as a fallback.
|
|
2085
|
+
*/
|
|
2086
|
+
type WideEventSamplingStrategy = "default" | "per_level";
|
|
2087
|
+
/**
|
|
2088
|
+
* Configuration for sampling wide event emissions.
|
|
2089
|
+
*
|
|
2090
|
+
* "error" and "fatal" levels default to a 100% keep rate, but can be
|
|
2091
|
+
* overridden by setting `perLevel` rates or via `shouldEmit`.
|
|
2092
|
+
*/
|
|
2093
|
+
interface WideEventSamplingConfig {
|
|
2094
|
+
/**
|
|
2095
|
+
* The sampling strategy.
|
|
2096
|
+
*
|
|
2097
|
+
* - `"default"` — a single `rate` applies to all non-error/fatal levels.
|
|
2098
|
+
* - `"per_level"` — per-level rates from the `perLevel` map; levels not in
|
|
2099
|
+
* the map use `rate` as a fallback.
|
|
2100
|
+
*
|
|
2101
|
+
* @default "default"
|
|
2102
|
+
*/
|
|
2103
|
+
strategy?: WideEventSamplingStrategy;
|
|
2104
|
+
/**
|
|
2105
|
+
* A rate between 0 and 1 that determines the fraction of events to keep.
|
|
2106
|
+
*
|
|
2107
|
+
* - `1` (or `true`) — keep 100% (sampling disabled)
|
|
2108
|
+
* - `0.1` — ~10% of events kept
|
|
2109
|
+
* - `0` (or `false`) — keep 0% (all dropped for sample-able levels)
|
|
2110
|
+
*
|
|
2111
|
+
* With `"default"` strategy this rate applies to all non-error/fatal levels.
|
|
2112
|
+
* With `"per_level"` strategy this field is **ignored** — unmapped levels
|
|
2113
|
+
* default to a 100% keep rate.
|
|
2114
|
+
*
|
|
2115
|
+
* @default 1
|
|
2116
|
+
*/
|
|
2117
|
+
rate?: boolean | number;
|
|
2118
|
+
/**
|
|
2119
|
+
* Per-level sampling rates when strategy is `"per_level"`.
|
|
2120
|
+
* Keys are log level strings (e.g. `"trace"`, `"info"`, `"warn"`).
|
|
2121
|
+
* Levels not listed use `rate` as a fallback.
|
|
2122
|
+
*
|
|
2123
|
+
* **Important:** "error" and "fatal" default to a 100% keep rate, but can be
|
|
2124
|
+
* explicitly overridden by setting their rate in `perLevel` or using a
|
|
2125
|
+
* `shouldEmit` callback.
|
|
2126
|
+
*
|
|
2127
|
+
* The map is snapshotted at construction time; mutating it afterward has
|
|
2128
|
+
* no effect.
|
|
2129
|
+
*
|
|
2130
|
+
* @example
|
|
2131
|
+
* ```ts
|
|
2132
|
+
* {
|
|
2133
|
+
* strategy: "per_level",
|
|
2134
|
+
* perLevel: {
|
|
2135
|
+
* trace: 0.1,
|
|
2136
|
+
* debug: 0.3,
|
|
2137
|
+
* info: 0.5,
|
|
2138
|
+
* },
|
|
2139
|
+
* }
|
|
2140
|
+
* // warn, error, fatal are all 100%
|
|
2141
|
+
* ```
|
|
2142
|
+
*/
|
|
2143
|
+
perLevel?: Partial<Record<LogLevelType, boolean | number>>;
|
|
2144
|
+
/**
|
|
2145
|
+
* A custom sampling callback that receives the wide event data and log
|
|
2146
|
+
* level, allowing you to make an informed keep/drop decision.
|
|
2147
|
+
*
|
|
2148
|
+
* When provided, this callback is invoked **in addition to** the built-in
|
|
2149
|
+
* rate-based sampling. An event is only kept when **both** the built-in
|
|
2150
|
+
* check passes (if `rate`/`perLevel` are configured) **and** the callback
|
|
2151
|
+
* returns `true`. If only `shouldEmit` is set (no `rate`), the callback
|
|
2152
|
+
* acts as the sole gate.
|
|
2153
|
+
*
|
|
2154
|
+
* **Note:** "error" and "fatal" default to a 100% keep rate, but can be
|
|
2155
|
+
* overridden by returning `false` from this callback.
|
|
2156
|
+
*
|
|
2157
|
+
* @example
|
|
2158
|
+
* ```ts
|
|
2159
|
+
* {
|
|
2160
|
+
* shouldEmit: ({ wideData, level }) => {
|
|
2161
|
+
* // Only emit wide events that have a userId
|
|
2162
|
+
* return !!wideData.userId;
|
|
2163
|
+
* },
|
|
2164
|
+
* }
|
|
2165
|
+
* ```
|
|
2166
|
+
*/
|
|
2167
|
+
shouldEmit?: (params: WideEventSamplingParams) => boolean;
|
|
2168
|
+
/**
|
|
2169
|
+
* Override the default log level when no explicit `level` is passed to
|
|
2170
|
+
* `emitWideEvent()`. When set, the resolved level uses this value instead
|
|
2171
|
+
* of `"info"`. This **does** affect sampling decisions — with `per_level`
|
|
2172
|
+
* strategy, the level from `emitLevel` determines which rate bucket applies.
|
|
2173
|
+
*
|
|
2174
|
+
* @default undefined (falls back to `"info"`)
|
|
2175
|
+
*/
|
|
2176
|
+
emitLevel?: LogLevelType;
|
|
2177
|
+
}
|
|
2063
2178
|
/**
|
|
2064
2179
|
* Configuration options for creating a wide event mixin.
|
|
2065
2180
|
*/
|
|
@@ -2101,6 +2216,22 @@ interface WideEventMixinOptions {
|
|
|
2101
2216
|
* @default false
|
|
2102
2217
|
*/
|
|
2103
2218
|
errorsAsArray?: boolean;
|
|
2219
|
+
/**
|
|
2220
|
+
* Optional: Sampling configuration for wide events.
|
|
2221
|
+
* When provided, the mixin will randomly decide whether to emit or skip wide
|
|
2222
|
+
* events based on the configured rate(s).
|
|
2223
|
+
*
|
|
2224
|
+
* "error" and "fatal" default to a 100% keep rate, but can be overridden
|
|
2225
|
+
* via `perLevel` rates or the `shouldEmit` callback.
|
|
2226
|
+
*
|
|
2227
|
+
* @example
|
|
2228
|
+
* // Keep ~10% of wide events (excluding errors/fatals)
|
|
2229
|
+
* { sampling: { strategy: "default", rate: 0.1 } }
|
|
2230
|
+
*
|
|
2231
|
+
* // Per-level: keep all trace/debug, but sample info at 5%
|
|
2232
|
+
* { sampling: { strategy: "per_level", perLevel: { info: 0.05 } } }
|
|
2233
|
+
*/
|
|
2234
|
+
sampling?: WideEventSamplingConfig;
|
|
2104
2235
|
}
|
|
2105
2236
|
/**
|
|
2106
2237
|
* Configuration for emitting a wide event.
|
|
@@ -2250,7 +2381,6 @@ declare module "@loglayer/shared" {
|
|
|
2250
2381
|
* wide event data across async boundaries. For Node.js, use `new AsyncLocalStorage()`
|
|
2251
2382
|
* from `async_hooks`. For browser environments, provide your own compatible implementation.
|
|
2252
2383
|
* @param options.includeContext - Whether to include withContext() data in emitted wide events (default: true)
|
|
2253
|
-
|
|
2254
2384
|
*
|
|
2255
2385
|
* @returns A LogLayer mixin registration object
|
|
2256
2386
|
*
|
|
@@ -2283,4 +2413,4 @@ declare module "@loglayer/shared" {
|
|
|
2283
2413
|
*/
|
|
2284
2414
|
declare function createWideEventMixin(options: WideEventMixinOptions): LogLayerMixinRegistration;
|
|
2285
2415
|
//#endregion
|
|
2286
|
-
export { type EmitWideEventConfig, type IWideEventMixin, type WideEventMixinOptions, createWideEventMixin };
|
|
2416
|
+
export { type EmitWideEventConfig, type IWideEventMixin, type WideEventMixinOptions, type WideEventSamplingConfig, type WideEventSamplingParams, type WideEventSamplingStrategy, createWideEventMixin };
|
package/dist/index.d.mts
CHANGED
|
@@ -2060,6 +2060,121 @@ declare class LogBuilder implements ILogBuilder<LogBuilder, boolean> {
|
|
|
2060
2060
|
*/
|
|
2061
2061
|
//#endregion
|
|
2062
2062
|
//#region src/types.d.ts
|
|
2063
|
+
/**
|
|
2064
|
+
* Parameters passed to a custom `shouldEmit` sampling callback.
|
|
2065
|
+
* Provides the accumulated wide event data and the log level so the
|
|
2066
|
+
* callback can make an informed keep/drop decision.
|
|
2067
|
+
*/
|
|
2068
|
+
interface WideEventSamplingParams {
|
|
2069
|
+
/**
|
|
2070
|
+
* The accumulated wide event data (wide events + optional context).
|
|
2071
|
+
* Contains the merged data that would be emitted if kept.
|
|
2072
|
+
*/
|
|
2073
|
+
wideData: Record<string, any>;
|
|
2074
|
+
/**
|
|
2075
|
+
* The log level that would be used for the emission.
|
|
2076
|
+
*/
|
|
2077
|
+
level: LogLevelType;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Sampling strategy for wide events.
|
|
2081
|
+
*
|
|
2082
|
+
* - `"default"` — a single `rate` applies to all non-error/fatal levels.
|
|
2083
|
+
* - `"per_level"` — per-level rates keyed by LogLevelType; levels not in the
|
|
2084
|
+
* map use `rate` as a fallback.
|
|
2085
|
+
*/
|
|
2086
|
+
type WideEventSamplingStrategy = "default" | "per_level";
|
|
2087
|
+
/**
|
|
2088
|
+
* Configuration for sampling wide event emissions.
|
|
2089
|
+
*
|
|
2090
|
+
* "error" and "fatal" levels default to a 100% keep rate, but can be
|
|
2091
|
+
* overridden by setting `perLevel` rates or via `shouldEmit`.
|
|
2092
|
+
*/
|
|
2093
|
+
interface WideEventSamplingConfig {
|
|
2094
|
+
/**
|
|
2095
|
+
* The sampling strategy.
|
|
2096
|
+
*
|
|
2097
|
+
* - `"default"` — a single `rate` applies to all non-error/fatal levels.
|
|
2098
|
+
* - `"per_level"` — per-level rates from the `perLevel` map; levels not in
|
|
2099
|
+
* the map use `rate` as a fallback.
|
|
2100
|
+
*
|
|
2101
|
+
* @default "default"
|
|
2102
|
+
*/
|
|
2103
|
+
strategy?: WideEventSamplingStrategy;
|
|
2104
|
+
/**
|
|
2105
|
+
* A rate between 0 and 1 that determines the fraction of events to keep.
|
|
2106
|
+
*
|
|
2107
|
+
* - `1` (or `true`) — keep 100% (sampling disabled)
|
|
2108
|
+
* - `0.1` — ~10% of events kept
|
|
2109
|
+
* - `0` (or `false`) — keep 0% (all dropped for sample-able levels)
|
|
2110
|
+
*
|
|
2111
|
+
* With `"default"` strategy this rate applies to all non-error/fatal levels.
|
|
2112
|
+
* With `"per_level"` strategy this field is **ignored** — unmapped levels
|
|
2113
|
+
* default to a 100% keep rate.
|
|
2114
|
+
*
|
|
2115
|
+
* @default 1
|
|
2116
|
+
*/
|
|
2117
|
+
rate?: boolean | number;
|
|
2118
|
+
/**
|
|
2119
|
+
* Per-level sampling rates when strategy is `"per_level"`.
|
|
2120
|
+
* Keys are log level strings (e.g. `"trace"`, `"info"`, `"warn"`).
|
|
2121
|
+
* Levels not listed use `rate` as a fallback.
|
|
2122
|
+
*
|
|
2123
|
+
* **Important:** "error" and "fatal" default to a 100% keep rate, but can be
|
|
2124
|
+
* explicitly overridden by setting their rate in `perLevel` or using a
|
|
2125
|
+
* `shouldEmit` callback.
|
|
2126
|
+
*
|
|
2127
|
+
* The map is snapshotted at construction time; mutating it afterward has
|
|
2128
|
+
* no effect.
|
|
2129
|
+
*
|
|
2130
|
+
* @example
|
|
2131
|
+
* ```ts
|
|
2132
|
+
* {
|
|
2133
|
+
* strategy: "per_level",
|
|
2134
|
+
* perLevel: {
|
|
2135
|
+
* trace: 0.1,
|
|
2136
|
+
* debug: 0.3,
|
|
2137
|
+
* info: 0.5,
|
|
2138
|
+
* },
|
|
2139
|
+
* }
|
|
2140
|
+
* // warn, error, fatal are all 100%
|
|
2141
|
+
* ```
|
|
2142
|
+
*/
|
|
2143
|
+
perLevel?: Partial<Record<LogLevelType, boolean | number>>;
|
|
2144
|
+
/**
|
|
2145
|
+
* A custom sampling callback that receives the wide event data and log
|
|
2146
|
+
* level, allowing you to make an informed keep/drop decision.
|
|
2147
|
+
*
|
|
2148
|
+
* When provided, this callback is invoked **in addition to** the built-in
|
|
2149
|
+
* rate-based sampling. An event is only kept when **both** the built-in
|
|
2150
|
+
* check passes (if `rate`/`perLevel` are configured) **and** the callback
|
|
2151
|
+
* returns `true`. If only `shouldEmit` is set (no `rate`), the callback
|
|
2152
|
+
* acts as the sole gate.
|
|
2153
|
+
*
|
|
2154
|
+
* **Note:** "error" and "fatal" default to a 100% keep rate, but can be
|
|
2155
|
+
* overridden by returning `false` from this callback.
|
|
2156
|
+
*
|
|
2157
|
+
* @example
|
|
2158
|
+
* ```ts
|
|
2159
|
+
* {
|
|
2160
|
+
* shouldEmit: ({ wideData, level }) => {
|
|
2161
|
+
* // Only emit wide events that have a userId
|
|
2162
|
+
* return !!wideData.userId;
|
|
2163
|
+
* },
|
|
2164
|
+
* }
|
|
2165
|
+
* ```
|
|
2166
|
+
*/
|
|
2167
|
+
shouldEmit?: (params: WideEventSamplingParams) => boolean;
|
|
2168
|
+
/**
|
|
2169
|
+
* Override the default log level when no explicit `level` is passed to
|
|
2170
|
+
* `emitWideEvent()`. When set, the resolved level uses this value instead
|
|
2171
|
+
* of `"info"`. This **does** affect sampling decisions — with `per_level`
|
|
2172
|
+
* strategy, the level from `emitLevel` determines which rate bucket applies.
|
|
2173
|
+
*
|
|
2174
|
+
* @default undefined (falls back to `"info"`)
|
|
2175
|
+
*/
|
|
2176
|
+
emitLevel?: LogLevelType;
|
|
2177
|
+
}
|
|
2063
2178
|
/**
|
|
2064
2179
|
* Configuration options for creating a wide event mixin.
|
|
2065
2180
|
*/
|
|
@@ -2101,6 +2216,22 @@ interface WideEventMixinOptions {
|
|
|
2101
2216
|
* @default false
|
|
2102
2217
|
*/
|
|
2103
2218
|
errorsAsArray?: boolean;
|
|
2219
|
+
/**
|
|
2220
|
+
* Optional: Sampling configuration for wide events.
|
|
2221
|
+
* When provided, the mixin will randomly decide whether to emit or skip wide
|
|
2222
|
+
* events based on the configured rate(s).
|
|
2223
|
+
*
|
|
2224
|
+
* "error" and "fatal" default to a 100% keep rate, but can be overridden
|
|
2225
|
+
* via `perLevel` rates or the `shouldEmit` callback.
|
|
2226
|
+
*
|
|
2227
|
+
* @example
|
|
2228
|
+
* // Keep ~10% of wide events (excluding errors/fatals)
|
|
2229
|
+
* { sampling: { strategy: "default", rate: 0.1 } }
|
|
2230
|
+
*
|
|
2231
|
+
* // Per-level: keep all trace/debug, but sample info at 5%
|
|
2232
|
+
* { sampling: { strategy: "per_level", perLevel: { info: 0.05 } } }
|
|
2233
|
+
*/
|
|
2234
|
+
sampling?: WideEventSamplingConfig;
|
|
2104
2235
|
}
|
|
2105
2236
|
/**
|
|
2106
2237
|
* Configuration for emitting a wide event.
|
|
@@ -2250,7 +2381,6 @@ declare module "@loglayer/shared" {
|
|
|
2250
2381
|
* wide event data across async boundaries. For Node.js, use `new AsyncLocalStorage()`
|
|
2251
2382
|
* from `async_hooks`. For browser environments, provide your own compatible implementation.
|
|
2252
2383
|
* @param options.includeContext - Whether to include withContext() data in emitted wide events (default: true)
|
|
2253
|
-
|
|
2254
2384
|
*
|
|
2255
2385
|
* @returns A LogLayer mixin registration object
|
|
2256
2386
|
*
|
|
@@ -2283,4 +2413,4 @@ declare module "@loglayer/shared" {
|
|
|
2283
2413
|
*/
|
|
2284
2414
|
declare function createWideEventMixin(options: WideEventMixinOptions): LogLayerMixinRegistration;
|
|
2285
2415
|
//#endregion
|
|
2286
|
-
export { type EmitWideEventConfig, type IWideEventMixin, type WideEventMixinOptions, createWideEventMixin };
|
|
2416
|
+
export { type EmitWideEventConfig, type IWideEventMixin, type WideEventMixinOptions, type WideEventSamplingConfig, type WideEventSamplingParams, type WideEventSamplingStrategy, createWideEventMixin };
|
package/dist/index.mjs
CHANGED
|
@@ -36,6 +36,45 @@ let LogLayerMixinAugmentType = /* @__PURE__ */ function(LogLayerMixinAugmentType
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/mixin.ts
|
|
38
38
|
/**
|
|
39
|
+
* Log levels that default to rate=1 (error/fatal) — can be overridden via `perLevel` or callback.
|
|
40
|
+
*/
|
|
41
|
+
const EXEMPT_LEVELS = new Set(["error", "fatal"]);
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a boolean or numeric rate to a number in [0, 1].
|
|
44
|
+
* `true` becomes 1, `false` becomes 0, numbers are clamped to [0, 1].
|
|
45
|
+
* `NaN`, `Infinity`, and `-Infinity` are treated as 0 (drop all).
|
|
46
|
+
*/
|
|
47
|
+
function toRate(value) {
|
|
48
|
+
if (value === void 0) return 1;
|
|
49
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
50
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
|
51
|
+
return Math.max(0, Math.min(1, value));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run rate-based sampling (default or per_level strategy).
|
|
55
|
+
* Returns true if the rate check passes, false if dropped.
|
|
56
|
+
* Returns true for error/fatal by default, unless their rate is explicitly set in `perLevel`.
|
|
57
|
+
*/
|
|
58
|
+
function runRateSampling(level, strategy, rate, perLevel) {
|
|
59
|
+
if (EXEMPT_LEVELS.has(level) && perLevel?.[level] === void 0) return true;
|
|
60
|
+
if (strategy === "per_level" && perLevel) {
|
|
61
|
+
const perRate = toRate(perLevel[level]);
|
|
62
|
+
if (perLevel[level] === void 0) {
|
|
63
|
+
const r = toRate(rate);
|
|
64
|
+
if (r === 1) return true;
|
|
65
|
+
if (r === 0) return false;
|
|
66
|
+
return Math.random() < r;
|
|
67
|
+
}
|
|
68
|
+
if (perRate === 1) return true;
|
|
69
|
+
if (perRate === 0) return false;
|
|
70
|
+
return Math.random() < perRate;
|
|
71
|
+
}
|
|
72
|
+
const r = toRate(rate);
|
|
73
|
+
if (r === 1) return true;
|
|
74
|
+
if (r === 0) return false;
|
|
75
|
+
return Math.random() < r;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
39
78
|
* Creates a LogLayer mixin that adds wide event logging functionality.
|
|
40
79
|
*
|
|
41
80
|
* Wide events are comprehensive, self-contained log entries that capture an entire
|
|
@@ -47,7 +86,6 @@ let LogLayerMixinAugmentType = /* @__PURE__ */ function(LogLayerMixinAugmentType
|
|
|
47
86
|
* wide event data across async boundaries. For Node.js, use `new AsyncLocalStorage()`
|
|
48
87
|
* from `async_hooks`. For browser environments, provide your own compatible implementation.
|
|
49
88
|
* @param options.includeContext - Whether to include withContext() data in emitted wide events (default: true)
|
|
50
|
-
|
|
51
89
|
*
|
|
52
90
|
* @returns A LogLayer mixin registration object
|
|
53
91
|
*
|
|
@@ -84,6 +122,14 @@ function createWideEventMixin(options) {
|
|
|
84
122
|
const wideEventField = options.wideEventField;
|
|
85
123
|
const errorsAsArray = options.errorsAsArray ?? false;
|
|
86
124
|
const errorField = options.errorField ?? (errorsAsArray ? "errors" : "error");
|
|
125
|
+
const snapshotPerLevel = options.sampling?.perLevel ? { ...options.sampling.perLevel } : void 0;
|
|
126
|
+
const samplingConfig = options.sampling ? {
|
|
127
|
+
strategy: options.sampling.strategy ?? "default",
|
|
128
|
+
rate: options.sampling.rate,
|
|
129
|
+
perLevel: snapshotPerLevel,
|
|
130
|
+
emitLevel: options.sampling.emitLevel,
|
|
131
|
+
shouldEmit: options.sampling.shouldEmit
|
|
132
|
+
} : void 0;
|
|
87
133
|
function defaultErrorSerializer(err) {
|
|
88
134
|
if (err instanceof Error) return {
|
|
89
135
|
name: err.name,
|
|
@@ -180,11 +226,20 @@ function createWideEventMixin(options) {
|
|
|
180
226
|
* Implementation for emitWideEvent
|
|
181
227
|
*/
|
|
182
228
|
function emitWideEventImpl(config, self) {
|
|
229
|
+
const defaultLevel = samplingConfig?.emitLevel ?? "info";
|
|
230
|
+
const level = config.level ?? defaultLevel;
|
|
231
|
+
if (samplingConfig && !samplingConfig.shouldEmit && !runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel)) return;
|
|
183
232
|
const store = asyncContext.getStore();
|
|
184
233
|
const wideEventData = {};
|
|
185
234
|
if (includeContext && store?._llContext) Object.assign(wideEventData, store._llContext);
|
|
186
235
|
if (store?._llWideEvents) Object.assign(wideEventData, store._llWideEvents);
|
|
187
|
-
|
|
236
|
+
if (samplingConfig?.shouldEmit) try {
|
|
237
|
+
const callbackOk = samplingConfig.shouldEmit({
|
|
238
|
+
wideData: wideEventData,
|
|
239
|
+
level
|
|
240
|
+
});
|
|
241
|
+
if (!runRateSampling(level, samplingConfig.strategy, samplingConfig.rate, samplingConfig.perLevel) || !callbackOk) return;
|
|
242
|
+
} catch {}
|
|
188
243
|
const metadataToEmit = wideEventField ? { [wideEventField]: wideEventData } : wideEventData;
|
|
189
244
|
self.withMetadata(metadataToEmit)[level](config.message);
|
|
190
245
|
}
|
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.1.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"tsdown": "0.22.0",
|
|
38
38
|
"typescript": "6.0.3",
|
|
39
39
|
"vitest": "4.1.7",
|
|
40
|
-
"@loglayer/shared": "4.2.0",
|
|
41
40
|
"@internal/tsconfig": "2.1.0",
|
|
41
|
+
"@loglayer/shared": "4.2.0",
|
|
42
42
|
"loglayer": "9.2.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {},
|