Apache Flagon UserALE.js provides a comprehensive JavaScript and TypeScript API for runtime configuration, custom log enrichment/filtering, header injection, and pluggable wire exporters.
options(newConfig?: Partial<Settings.Config>): Settings.ConfigReads or shallow-merges runtime configuration options. Calling options() with no arguments returns the current configuration object.
import * as userale from 'flagon-userale';
userale.options({
url: 'https://collector.example.com/v1/logs',
userId: 'user-42',
toolName: 'checkout-ui',
toolVersion: '2.5.0',
exporterFormat: 'otlp-json',
});
start(): void & stop(): voidStarts or halts event capturing and log transmission. Particularly useful when autostart: false (data-autostart="false") is configured until user consent or authentication completes.
userale.options({ autostart: false });
// Later, after user consent:
userale.start();
version: stringExports the current flagon-userale package version string.
addCallbacks(callbacks: Record<string, CallableFunction>): booleanRegisters one or more named callback functions executed sequentially on every packaged log ((log, event) => log | false). Returning false (or any falsy value) drops the log from the queue; returning a modified log object replaces it.
userale.addCallbacks({
dropIntervalLogs(log) {
if (log.logType === 'interval') {
return false;
}
return log;
},
attachExperimentTag(log, event) {
return {
...log,
experimentVariant: 'checkout-v2',
};
},
});
removeCallbacks(targetKeys: string[]): voidRemoves registered callbacks by name:
userale.removeCallbacks(['dropIntervalLogs']);
log(customLog: Logging.CustomLog): booleanAppends a custom log object directly onto the UserALE queue:
userale.log({
target: 'checkout-funnel',
type: 'step_complete',
logType: 'custom',
userAction: false,
details: { step: 2, cartTotal: 149.0 },
});
packageLog(e: Event, detailFcn?: Logging.DynamicDetailFunction): boolean & packageCustomLog(customLogs: Logging.CustomLog, detailFcn: Logging.DynamicDetailFunction, userAction: boolean): booleanPackages a standard DOM Event or custom metadata object with full UserALE page/session/browser context and enqueues it.
details(config: Configuration, type: Events.AllowedEvents): ((e: Event) => Record<string, unknown>) | nullReturns the default detail-extractor function for a given DOM event type (click, keydown, input, change, wheel, etc.). Note that <input type="password"> values are automatically masked ("********").
getSelector(ele: EventTarget): string & buildPath(e: Event): string[]Utility helpers that compute a CSS-like element selector (tag#id.class) and full DOM ancestry path array from a DOM Event.
getTargetRect(el: EventTarget | null): TargetRect | null & detectBrowser(): { browser: string; version: string }getTargetRect extracts a rounded bounding box ({ x, y, width, height, top, right, bottom, left }) for a DOM element (attached automatically as targetRect on click, dblclick, change, and submit events).detectBrowser detects the browser name and version using navigator.userAgentData (User-Agent Client Hints) with a regex fallback and zero external dependencies.All HTTP-based exporters (HttpJsonExporter, ProtoJsonExporter, OtlpJsonExporter) construct headers through buildHeaders(config), applying them to both interval fetch() requests and pagehide (fetch(..., { keepalive: true })) flushes.
registerAuthCallback(callback: () => string): boolean & resetAuthCallback(): voidRegisters (or resets) a dynamic provider returning the Authorization header value on each batch export:
userale.registerAuthCallback(() => `Bearer ${getCurrentJwtToken()}`);
registerHeadersCallback(callback: () => Record<string, string>): boolean & resetHeadersCallback(): voidRegisters (or resets) a dynamic provider returning arbitrary HTTP headers on each batch export:
userale.registerHeadersCallback(() => ({
'x-tenant-id': 'acme-corp',
'x-request-source': 'userale-web',
}));
Static headers can also be configured via userale.options({ authHeader: 'Bearer ...', apiKey: '...', headers: { ... } }).
LogExporter)UserALE decouples in-memory event packaging from wire serialization and transport via the LogExporter interface:
export interface LogExporter {
export(logs: Array<Logging.Log>, options?: ExportOptions): Promise<ExportResult>;
flush?(): Promise<void>;
shutdown?(): Promise<void>;
}
| Exported Symbol | Description |
|---|---|
registerExporter(exporter: LogExporter) |
Registers a custom LogExporter instance and switches the active sender to use it immediately. |
createExporter(config) |
Factory returning the active LogExporter for a Configuration instance. |
HttpJsonExporter |
Default exporter sending flat UserALE JSON arrays over fetch (with 48 KiB chunked keepalive: true on pagehide). |
WebSocketExporter |
Streams log batches over a persistent WebSocket (ws:// / wss://). |
ProtoJsonExporter / toProtoLogBatch(logs) / toProtoUserALELog(log) |
Serializes logs into canonical Protobuf v1 JSON (apache.flagon.userale.v1.LogBatch) matching proto/apache/flagon/userale/v1/log.proto. |
OtlpJsonExporter / toOtlpExportLogsServiceRequest(logs, config) |
Serializes logs into OpenTelemetry ExportLogsServiceRequest JSON with Resource hoisting, BigInt nanosecond timestamps, eventName, structured kvlistValue body, and W3C traceId/spanId correlation. |
OtelSdkExporter |
Bridges UserALE logs directly into an existing OpenTelemetry JS @opentelemetry/api-logs Logger via logger.emit(...). |
resolveOtlpLogsEndpoint(endpoint) |
Normalizes an OTLP base URL by appending /v1/logs when the URL path is empty or /. |
toUnixNanoString(clientTimeMs, microTime) |
Converts millisecond + sub-millisecond timestamps to a 64-bit nanosecond string using BigInt. |
import * as userale from 'flagon-userale';
userale.options({
exporterType: 'otlp',
otlpEndpoint: 'http://localhost:4318/v1/logs',
toolName: 'storefront-web',
traceContext: () => ({
traceId: currentActiveSpan.traceId, // 32-hex W3C trace-id
spanId: currentActiveSpan.spanId, // 16-hex W3C span-id
}),
});
#104)UserALE provides a two-layer session replay architecture that keeps the core bundle dependency-free while supporting both structural DOM telemetry and rrweb-compatible visual playback:
| Exported Symbol | Description |
|---|---|
startSessionReplay(options?: SessionReplayOptions, config?: Configuration): () => void |
Starts Layer 1 (MutationObserver + RageClickDetector) and optional Layer 2 (rrweb ring-buffer recorder). Returns a stop/cleanup function. |
flushSessionReplay(trigger?: string): Promise<ReplayEnvelope \| null> |
Immediately flushes the rolling rrweb ring buffer as a logType: 'replay', type: 'session_replay' envelope tagged with sessionId and active OpenTelemetry traceId / spanId. |
stopSessionReplay(): void |
Stops active MutationObserver and rrweb recording and clears the ring buffer. |
getActiveReplayRingBuffer(): ReplayRingBuffer \| null |
Returns the active ReplayRingBuffer instance when session replay is running. |
ReplayRingBuffer |
Rolling in-memory ring buffer (maxDurationMs, maxFrames) that preserves rrweb Meta (type: 4) and latest FullSnapshot (type: 2) anchor frames. |
RageClickDetector |
Sliding-window detector that triggers when threshold (default 3) clicks occur on the same target selector within windowMs (1000ms) and maxRadiusPx (30px). |
import * as userale from 'flagon-userale';
import * as rrweb from 'rrweb';
userale.startSessionReplay({
recorder: rrweb.record,
maxDurationMs: 30_000,
observeMutations: true,
detectRageClicks: true,
flushOnError: true,
});
Contributions are welcome! See our Contributing Guide or open an issue on GitHub.