autotel-edge 3.16.13 → 3.16.15
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-0FktBGyj.js +290 -0
- package/dist/config-0FktBGyj.js.map +1 -0
- package/dist/events.d.ts +42 -43
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +144 -137
- package/dist/events.js.map +1 -1
- package/dist/execution-logger-BWlya70r.d.ts +137 -0
- package/dist/execution-logger-BWlya70r.d.ts.map +1 -0
- package/dist/execution-logger-CoxSsBmU.js +247 -0
- package/dist/execution-logger-CoxSsBmU.js.map +1 -0
- package/dist/index.d.ts +192 -276
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +930 -1060
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +122 -136
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +783 -774
- package/dist/logger.js.map +1 -1
- package/dist/parse-error.d.ts +12 -10
- package/dist/parse-error.d.ts.map +1 -0
- package/dist/parse-error.js +55 -2
- package/dist/parse-error.js.map +1 -1
- package/dist/sampling.d.ts +51 -76
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +155 -90
- package/dist/sampling.js.map +1 -1
- package/dist/testing.d.ts +1 -2
- package/dist/testing.js +1 -3
- package/dist/types-HCbsjZzp.d.ts +214 -0
- package/dist/types-HCbsjZzp.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/core/buffer.ts +4 -3
- package/src/core/provider.context.test.ts +55 -0
- package/src/core/provider.ts +18 -1
- package/binding.gyp +0 -9
- package/dist/chunk-AL7ZD64M.js +0 -291
- package/dist/chunk-AL7ZD64M.js.map +0 -1
- package/dist/chunk-INQQQVXN.js +0 -310
- package/dist/chunk-INQQQVXN.js.map +0 -1
- package/dist/chunk-M7Z4P5MC.js +0 -64
- package/dist/chunk-M7Z4P5MC.js.map +0 -1
- package/dist/execution-logger-77TRRoWn.d.ts +0 -84
- package/dist/testing.js.map +0 -1
- package/dist/types-uulICZo7.d.ts +0 -216
- package/index.js +0 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
|
+
|
|
3
|
+
//#region src/core/trace-context.ts
|
|
4
|
+
/**
|
|
5
|
+
* WeakMap to store span names for active spans.
|
|
6
|
+
* Enables retrieving span names for correlation helpers.
|
|
7
|
+
*/
|
|
8
|
+
const spanNameMap = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
/**
|
|
10
|
+
* Create a TraceContext from an OpenTelemetry Span
|
|
11
|
+
*
|
|
12
|
+
* This utility extracts trace context information from a span
|
|
13
|
+
* and provides span manipulation methods in a consistent format.
|
|
14
|
+
*/
|
|
15
|
+
function createTraceContext(span) {
|
|
16
|
+
const spanContext = span.spanContext();
|
|
17
|
+
return {
|
|
18
|
+
traceId: spanContext.traceId,
|
|
19
|
+
spanId: spanContext.spanId,
|
|
20
|
+
correlationId: spanContext.traceId.slice(0, 16),
|
|
21
|
+
"code.function": spanNameMap.get(span),
|
|
22
|
+
setAttribute: span.setAttribute.bind(span),
|
|
23
|
+
setAttributes: span.setAttributes.bind(span),
|
|
24
|
+
setStatus: span.setStatus.bind(span),
|
|
25
|
+
recordException: span.recordException.bind(span),
|
|
26
|
+
addEvent: span.addEvent.bind(span),
|
|
27
|
+
addLink: span.addLink.bind(span),
|
|
28
|
+
addLinks: span.addLinks.bind(span),
|
|
29
|
+
updateName: span.updateName.bind(span),
|
|
30
|
+
isRecording: span.isRecording.bind(span)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Store the span name for later retrieval via trace context helpers.
|
|
35
|
+
*/
|
|
36
|
+
function setSpanName(span, name) {
|
|
37
|
+
spanNameMap.set(span, name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/execution-logger.ts
|
|
42
|
+
const POST_EMIT_FORK_HINT = "For intentional background work tied to this execution, use log.fork('label', fn) when available.";
|
|
43
|
+
function warnPostEmit(method, detail) {
|
|
44
|
+
console.warn(`[autotel-edge] ${method} called after the execution event was emitted - ${detail} This data will not appear in observability. ${POST_EMIT_FORK_HINT}`);
|
|
45
|
+
}
|
|
46
|
+
function mergeInto(target, source) {
|
|
47
|
+
for (const key in source) {
|
|
48
|
+
const sourceVal = source[key];
|
|
49
|
+
if (sourceVal === void 0) continue;
|
|
50
|
+
const targetVal = target[key];
|
|
51
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) mergeInto(targetVal, sourceVal);
|
|
52
|
+
else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) target[key] = [...targetVal, ...sourceVal];
|
|
53
|
+
else target[key] = sourceVal;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function generateCorrelationId() {
|
|
57
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") return globalThis.crypto.randomUUID();
|
|
58
|
+
return `exec-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
59
|
+
}
|
|
60
|
+
function toAttributeValue(value) {
|
|
61
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
if (value.every((item) => typeof item === "string") || value.every((item) => typeof item === "number") || value.every((item) => typeof item === "boolean")) return value;
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(value);
|
|
66
|
+
} catch {
|
|
67
|
+
return "<serialization-failed>";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (value instanceof Date) return value.toISOString();
|
|
71
|
+
if (value instanceof Error) return value.message;
|
|
72
|
+
}
|
|
73
|
+
function flattenToAttributes(fields, prefix = "") {
|
|
74
|
+
const out = {};
|
|
75
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
76
|
+
function flatten(obj, currentPrefix) {
|
|
77
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
78
|
+
if (value == null) continue;
|
|
79
|
+
const nextKey = currentPrefix ? `${currentPrefix}.${key}` : key;
|
|
80
|
+
const attr = toAttributeValue(value);
|
|
81
|
+
if (attr !== void 0) {
|
|
82
|
+
out[nextKey] = attr;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === "object" && value.constructor === Object) {
|
|
86
|
+
if (seen.has(value)) {
|
|
87
|
+
out[nextKey] = "<circular-reference>";
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
seen.add(value);
|
|
91
|
+
flatten(value, nextKey);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
out[nextKey] = JSON.stringify(value);
|
|
96
|
+
} catch {
|
|
97
|
+
out[nextKey] = "<serialization-failed>";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
flatten(fields, prefix);
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function getErrorAttributes(error) {
|
|
105
|
+
const attributes = {
|
|
106
|
+
"error.type": error.name || "Error",
|
|
107
|
+
"error.message": error.message
|
|
108
|
+
};
|
|
109
|
+
if (error.stack) attributes["error.stack"] = error.stack;
|
|
110
|
+
const structured = error;
|
|
111
|
+
if (structured.why) attributes["error.why"] = structured.why;
|
|
112
|
+
if (structured.fix) attributes["error.fix"] = structured.fix;
|
|
113
|
+
if (structured.link) attributes["error.link"] = structured.link;
|
|
114
|
+
if (structured.code !== void 0) attributes["error.code"] = typeof structured.code === "string" ? structured.code : String(structured.code);
|
|
115
|
+
if (structured.status !== void 0) attributes["error.status"] = structured.status;
|
|
116
|
+
if (structured.details) Object.assign(attributes, flattenToAttributes(structured.details, "error.details"));
|
|
117
|
+
return attributes;
|
|
118
|
+
}
|
|
119
|
+
function resolveContext(ctx) {
|
|
120
|
+
if (ctx) return ctx;
|
|
121
|
+
const span = trace.getActiveSpan();
|
|
122
|
+
if (!span) throw new Error("[autotel-edge] getExecutionLogger() requires an active span or explicit TraceContext. Wrap your handler with trace() or pass ctx directly.");
|
|
123
|
+
return createTraceContext(span);
|
|
124
|
+
}
|
|
125
|
+
function getExecutionLogger(ctx, options) {
|
|
126
|
+
const activeContext = resolveContext(ctx);
|
|
127
|
+
let contextState = {};
|
|
128
|
+
let emitted = false;
|
|
129
|
+
let lastSnapshot = null;
|
|
130
|
+
const addLogEvent = (level, message, fields) => {
|
|
131
|
+
const attrs = fields ? flattenToAttributes(fields) : void 0;
|
|
132
|
+
activeContext.addEvent(`log.${level}`, {
|
|
133
|
+
message,
|
|
134
|
+
...attrs
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const sealCheck = (method, keys) => {
|
|
138
|
+
if (emitted) warnPostEmit(method, `Keys dropped: ${keys.length ? keys.join(", ") : "(empty)"}.`);
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
set(fields) {
|
|
142
|
+
sealCheck("log.set()", Object.keys(fields));
|
|
143
|
+
if (emitted) return;
|
|
144
|
+
mergeInto(contextState, fields);
|
|
145
|
+
activeContext.setAttributes(flattenToAttributes(fields));
|
|
146
|
+
},
|
|
147
|
+
info(message, fields) {
|
|
148
|
+
sealCheck("log.info()", fields ? ["message", ...Object.keys(fields).filter((k) => k !== "requestLogs")] : ["message"]);
|
|
149
|
+
if (emitted) return;
|
|
150
|
+
addLogEvent("info", message, fields);
|
|
151
|
+
if (fields) {
|
|
152
|
+
mergeInto(contextState, fields);
|
|
153
|
+
activeContext.setAttributes(flattenToAttributes(fields));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
warn(message, fields) {
|
|
157
|
+
sealCheck("log.warn()", fields ? ["message", ...Object.keys(fields).filter((k) => k !== "requestLogs")] : ["message"]);
|
|
158
|
+
if (emitted) return;
|
|
159
|
+
addLogEvent("warn", message, fields);
|
|
160
|
+
activeContext.setAttribute("autotel.log.level", "warn");
|
|
161
|
+
if (fields) {
|
|
162
|
+
mergeInto(contextState, fields);
|
|
163
|
+
activeContext.setAttributes(flattenToAttributes(fields));
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
error(error, fields) {
|
|
167
|
+
sealCheck("log.error()", fields ? [...Object.keys(fields), "error"] : ["error"]);
|
|
168
|
+
if (emitted) return;
|
|
169
|
+
const err = typeof error === "string" ? new Error(error) : error;
|
|
170
|
+
activeContext.recordException(err);
|
|
171
|
+
activeContext.setStatus({
|
|
172
|
+
code: SpanStatusCode.ERROR,
|
|
173
|
+
message: err.message
|
|
174
|
+
});
|
|
175
|
+
activeContext.setAttributes(getErrorAttributes(err));
|
|
176
|
+
addLogEvent("error", err.message, fields);
|
|
177
|
+
if (fields) {
|
|
178
|
+
mergeInto(contextState, fields);
|
|
179
|
+
activeContext.setAttributes(flattenToAttributes(fields));
|
|
180
|
+
}
|
|
181
|
+
activeContext.setAttribute("autotel.log.level", "error");
|
|
182
|
+
},
|
|
183
|
+
getContext() {
|
|
184
|
+
return { ...contextState };
|
|
185
|
+
},
|
|
186
|
+
emitNow(overrides) {
|
|
187
|
+
if (emitted) {
|
|
188
|
+
warnPostEmit("log.emitNow()", "Ignoring duplicate emit.");
|
|
189
|
+
return lastSnapshot;
|
|
190
|
+
}
|
|
191
|
+
const mergedContext = {
|
|
192
|
+
...contextState,
|
|
193
|
+
...overrides ?? {}
|
|
194
|
+
};
|
|
195
|
+
const flattened = flattenToAttributes(mergedContext);
|
|
196
|
+
activeContext.setAttributes(flattened);
|
|
197
|
+
const snapshot = {
|
|
198
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
199
|
+
traceId: activeContext.traceId,
|
|
200
|
+
spanId: activeContext.spanId,
|
|
201
|
+
correlationId: activeContext.correlationId,
|
|
202
|
+
context: mergedContext
|
|
203
|
+
};
|
|
204
|
+
activeContext.addEvent("log.emit.manual", { ...flattened });
|
|
205
|
+
if (options?.onEmit) Promise.resolve(options.onEmit(snapshot)).catch((error) => {
|
|
206
|
+
console.warn("[autotel-edge] execution logger onEmit failed:", error);
|
|
207
|
+
});
|
|
208
|
+
emitted = true;
|
|
209
|
+
lastSnapshot = snapshot;
|
|
210
|
+
return snapshot;
|
|
211
|
+
},
|
|
212
|
+
fork(label, fn, forkOptions) {
|
|
213
|
+
const parentCorrelationId = activeContext.correlationId;
|
|
214
|
+
if (typeof parentCorrelationId !== "string" || parentCorrelationId.length === 0) throw new Error("[autotel-edge] log.fork() requires the parent logger to have a correlationId. Ensure execution context was created by autotel trace instrumentation.");
|
|
215
|
+
const lifecycle = forkOptions?.lifecycle;
|
|
216
|
+
trace.getTracer("autotel-edge.execution-logger").startActiveSpan(`execution.fork:${label}`, (childSpan) => {
|
|
217
|
+
const childLog = getExecutionLogger({
|
|
218
|
+
...createTraceContext(childSpan),
|
|
219
|
+
correlationId: generateCorrelationId()
|
|
220
|
+
});
|
|
221
|
+
childLog.set({
|
|
222
|
+
operation: label,
|
|
223
|
+
_parentCorrelationId: parentCorrelationId
|
|
224
|
+
});
|
|
225
|
+
lifecycle?.onChildEnter?.(childLog);
|
|
226
|
+
return Promise.resolve().then(() => fn()).then(() => {
|
|
227
|
+
childLog.emitNow();
|
|
228
|
+
}).catch((err) => {
|
|
229
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
230
|
+
childLog.error(error);
|
|
231
|
+
childLog.emitNow();
|
|
232
|
+
}).finally(() => {
|
|
233
|
+
try {
|
|
234
|
+
lifecycle?.onChildExit?.(childLog);
|
|
235
|
+
} catch (hookError) {
|
|
236
|
+
console.warn("[autotel-edge] fork onChildExit hook threw:", hookError);
|
|
237
|
+
}
|
|
238
|
+
childSpan.end();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
export { createTraceContext as n, setSpanName as r, getExecutionLogger as t };
|
|
247
|
+
//# sourceMappingURL=execution-logger-CoxSsBmU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-logger-CoxSsBmU.js","names":["otelTrace"],"sources":["../src/core/trace-context.ts","../src/execution-logger.ts"],"sourcesContent":["/**\n * Trace context types and utilities\n */\n\nimport type {\n AttributeValue,\n Link,\n Span,\n SpanStatusCode,\n TimeInput,\n} from '@opentelemetry/api';\n\n/**\n * WeakMap to store span names for active spans.\n * Enables retrieving span names for correlation helpers.\n */\nconst spanNameMap = new WeakMap<Span, string>();\n\n/**\n * Base trace context containing trace identifiers\n */\nexport interface TraceContextBase {\n traceId: string;\n spanId: string;\n correlationId: string;\n 'code.function'?: string;\n}\n\n/**\n * Span methods available on trace context\n */\nexport interface SpanMethods {\n /** Set a single attribute on the span */\n setAttribute(key: string, value: AttributeValue): void;\n /** Set multiple attributes on the span */\n setAttributes(attrs: Record<string, AttributeValue>): void;\n /** Set the status of the span */\n setStatus(status: { code: SpanStatusCode; message?: string }): void;\n /** Record an exception on the span */\n recordException(exception: Error, time?: TimeInput): void;\n /** Add an event to the span (for logging milestones/checkpoints) */\n addEvent(\n name: string,\n attributesOrStartTime?: Record<string, AttributeValue> | TimeInput,\n startTime?: TimeInput,\n ): void;\n /** Add a link to another span */\n addLink(link: Link): void;\n /** Add multiple links to other spans */\n addLinks(links: Link[]): void;\n /** Update the span name dynamically */\n updateName(name: string): void;\n /** Check if the span is recording */\n isRecording(): boolean;\n}\n\n/**\n * Complete trace context that merges base context and span methods\n *\n * This is the ctx parameter passed to factory functions in trace().\n * It provides access to trace IDs and span manipulation methods.\n */\nexport type TraceContext = TraceContextBase & SpanMethods;\n\n/**\n * Create a TraceContext from an OpenTelemetry Span\n *\n * This utility extracts trace context information from a span\n * and provides span manipulation methods in a consistent format.\n */\nexport function createTraceContext(span: Span): TraceContext {\n const spanContext = span.spanContext();\n return {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n correlationId: spanContext.traceId.slice(0, 16),\n 'code.function': spanNameMap.get(span),\n setAttribute: span.setAttribute.bind(span),\n setAttributes: span.setAttributes.bind(span),\n setStatus: span.setStatus.bind(span),\n recordException: span.recordException.bind(span),\n addEvent: span.addEvent.bind(span),\n addLink: span.addLink.bind(span),\n addLinks: span.addLinks.bind(span),\n updateName: span.updateName.bind(span),\n isRecording: span.isRecording.bind(span),\n };\n}\n\n/**\n * Store the span name for later retrieval via trace context helpers.\n */\nexport function setSpanName(span: Span, name: string): void {\n spanNameMap.set(span, name);\n}\n","import { trace as otelTrace, SpanStatusCode } from '@opentelemetry/api';\nimport type { AttributeValue } from '@opentelemetry/api';\nimport type { TraceContext } from './functional';\nimport { createTraceContext } from './core/trace-context';\n\nconst POST_EMIT_FORK_HINT =\n \"For intentional background work tied to this execution, use log.fork('label', fn) when available.\";\n\nfunction warnPostEmit(method: string, detail: string): void {\n console.warn(\n `[autotel-edge] ${method} called after the execution event was emitted - ${detail} This data will not appear in observability. ${POST_EMIT_FORK_HINT}`,\n );\n}\n\nfunction mergeInto(\n target: Record<string, unknown>,\n source: Record<string, unknown>,\n): void {\n for (const key in source) {\n const sourceVal = source[key];\n if (sourceVal === undefined) continue;\n const targetVal = target[key];\n if (\n sourceVal !== null &&\n typeof sourceVal === 'object' &&\n !Array.isArray(sourceVal) &&\n targetVal !== null &&\n typeof targetVal === 'object' &&\n !Array.isArray(targetVal)\n ) {\n mergeInto(\n targetVal as Record<string, unknown>,\n sourceVal as Record<string, unknown>,\n );\n } else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {\n target[key] = [...targetVal, ...sourceVal];\n } else {\n target[key] = sourceVal;\n }\n }\n}\n\nfunction generateCorrelationId(): string {\n if (\n typeof globalThis.crypto !== 'undefined' &&\n typeof globalThis.crypto.randomUUID === 'function'\n ) {\n return globalThis.crypto.randomUUID();\n }\n\n return `exec-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\n/**\n * Optional lifecycle hooks for adapters that need to track child loggers\n * spawned by `log.fork()` (e.g. activeLoggers maps in framework integrations).\n */\nexport interface ForkLifecycle {\n /** Called after the child logger is created, before `fn` runs. */\n onChildEnter?: (child: ExecutionLogger) => void;\n /** Called after the child has finished (emit + drain), success or failure. */\n onChildExit?: (child: ExecutionLogger) => void;\n}\n\nexport interface ForkOptions {\n lifecycle?: ForkLifecycle;\n}\n\nexport interface ExecutionLogger {\n set(fields: Record<string, unknown>): void;\n info(message: string, fields?: Record<string, unknown>): void;\n warn(message: string, fields?: Record<string, unknown>): void;\n error(error: Error | string, fields?: Record<string, unknown>): void;\n getContext(): Record<string, unknown>;\n emitNow(overrides?: Record<string, unknown>): ExecutionLogSnapshot;\n fork(\n label: string,\n fn: () => void | Promise<void>,\n options?: ForkOptions,\n ): void;\n}\n\nexport interface ExecutionLogSnapshot {\n timestamp: string;\n traceId: string;\n spanId: string;\n correlationId: string;\n context: Record<string, unknown>;\n}\n\nexport interface ExecutionLoggerOptions {\n onEmit?: (snapshot: ExecutionLogSnapshot) => void | Promise<void>;\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue | undefined {\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n\n if (Array.isArray(value)) {\n if (\n value.every((item) => typeof item === 'string') ||\n value.every((item) => typeof item === 'number') ||\n value.every((item) => typeof item === 'boolean')\n ) {\n return value as AttributeValue;\n }\n\n try {\n return JSON.stringify(value);\n } catch {\n return '<serialization-failed>';\n }\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (value instanceof Error) {\n return value.message;\n }\n\n return undefined;\n}\n\nfunction flattenToAttributes(\n fields: Record<string, unknown>,\n prefix = '',\n): Record<string, AttributeValue> {\n const out: Record<string, AttributeValue> = {};\n const seen = new WeakSet<object>();\n\n function flatten(obj: Record<string, unknown>, currentPrefix: string): void {\n for (const [key, value] of Object.entries(obj)) {\n if (value == null) continue;\n\n const nextKey = currentPrefix ? `${currentPrefix}.${key}` : key;\n const attr = toAttributeValue(value);\n\n if (attr !== undefined) {\n out[nextKey] = attr;\n continue;\n }\n\n if (typeof value === 'object' && value.constructor === Object) {\n if (seen.has(value)) {\n out[nextKey] = '<circular-reference>';\n continue;\n }\n\n seen.add(value);\n flatten(value as Record<string, unknown>, nextKey);\n continue;\n }\n\n try {\n out[nextKey] = JSON.stringify(value);\n } catch {\n out[nextKey] = '<serialization-failed>';\n }\n }\n }\n\n flatten(fields, prefix);\n return out;\n}\n\nfunction getErrorAttributes(error: Error): Record<string, AttributeValue> {\n const attributes: Record<string, AttributeValue> = {\n 'error.type': error.name || 'Error',\n 'error.message': error.message,\n };\n\n if (error.stack) {\n attributes['error.stack'] = error.stack;\n }\n\n const structured = error as Error & {\n why?: string;\n fix?: string;\n link?: string;\n code?: string | number;\n status?: number;\n details?: Record<string, unknown>;\n };\n\n if (structured.why) attributes['error.why'] = structured.why;\n if (structured.fix) attributes['error.fix'] = structured.fix;\n if (structured.link) attributes['error.link'] = structured.link;\n if (structured.code !== undefined) {\n attributes['error.code'] =\n typeof structured.code === 'string'\n ? structured.code\n : String(structured.code);\n }\n if (structured.status !== undefined) {\n attributes['error.status'] = structured.status;\n }\n if (structured.details) {\n Object.assign(\n attributes,\n flattenToAttributes(structured.details, 'error.details'),\n );\n }\n\n return attributes;\n}\n\nfunction resolveContext(ctx?: TraceContext): TraceContext {\n if (ctx) return ctx;\n\n const span = otelTrace.getActiveSpan();\n if (!span) {\n throw new Error(\n '[autotel-edge] getExecutionLogger() requires an active span or explicit TraceContext. Wrap your handler with trace() or pass ctx directly.',\n );\n }\n\n return createTraceContext(span);\n}\n\nexport function getExecutionLogger(\n ctx?: TraceContext,\n options?: ExecutionLoggerOptions,\n): ExecutionLogger {\n const activeContext = resolveContext(ctx);\n let contextState: Record<string, unknown> = {};\n let emitted = false;\n let lastSnapshot: ExecutionLogSnapshot | null = null;\n\n const addLogEvent = (\n level: 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n ) => {\n const attrs = fields ? flattenToAttributes(fields) : undefined;\n activeContext.addEvent(`log.${level}`, {\n message,\n ...attrs,\n });\n };\n\n const sealCheck = (method: string, keys: string[]): void => {\n if (emitted) {\n warnPostEmit(\n method,\n `Keys dropped: ${keys.length ? keys.join(', ') : '(empty)'}.`,\n );\n }\n };\n\n return {\n set(fields: Record<string, unknown>) {\n sealCheck('log.set()', Object.keys(fields));\n if (emitted) return;\n mergeInto(contextState, fields);\n activeContext.setAttributes(flattenToAttributes(fields));\n },\n\n info(message: string, fields?: Record<string, unknown>) {\n const keys = fields\n ? ['message', ...Object.keys(fields).filter((k) => k !== 'requestLogs')]\n : ['message'];\n sealCheck('log.info()', keys);\n if (emitted) return;\n addLogEvent('info', message, fields);\n if (fields) {\n mergeInto(contextState, fields);\n activeContext.setAttributes(flattenToAttributes(fields));\n }\n },\n\n warn(message: string, fields?: Record<string, unknown>) {\n const keys = fields\n ? ['message', ...Object.keys(fields).filter((k) => k !== 'requestLogs')]\n : ['message'];\n sealCheck('log.warn()', keys);\n if (emitted) return;\n addLogEvent('warn', message, fields);\n activeContext.setAttribute('autotel.log.level', 'warn');\n if (fields) {\n mergeInto(contextState, fields);\n activeContext.setAttributes(flattenToAttributes(fields));\n }\n },\n\n error(error: Error | string, fields?: Record<string, unknown>) {\n const keys = fields ? [...Object.keys(fields), 'error'] : ['error'];\n sealCheck('log.error()', keys);\n if (emitted) return;\n const err = typeof error === 'string' ? new Error(error) : error;\n\n activeContext.recordException(err);\n activeContext.setStatus({\n code: SpanStatusCode.ERROR,\n message: err.message,\n });\n activeContext.setAttributes(getErrorAttributes(err));\n addLogEvent('error', err.message, fields);\n\n if (fields) {\n mergeInto(contextState, fields);\n activeContext.setAttributes(flattenToAttributes(fields));\n }\n\n activeContext.setAttribute('autotel.log.level', 'error');\n },\n\n getContext() {\n return { ...contextState };\n },\n\n emitNow(overrides?: Record<string, unknown>): ExecutionLogSnapshot {\n if (emitted) {\n warnPostEmit('log.emitNow()', 'Ignoring duplicate emit.');\n return lastSnapshot as ExecutionLogSnapshot;\n }\n\n const mergedContext = {\n ...contextState,\n ...(overrides ?? {}),\n };\n const flattened = flattenToAttributes(mergedContext);\n activeContext.setAttributes(flattened);\n\n const snapshot: ExecutionLogSnapshot = {\n timestamp: new Date().toISOString(),\n traceId: activeContext.traceId,\n spanId: activeContext.spanId,\n correlationId: activeContext.correlationId,\n context: mergedContext,\n };\n\n activeContext.addEvent('log.emit.manual', {\n ...flattened,\n });\n\n if (options?.onEmit) {\n Promise.resolve(options.onEmit(snapshot)).catch((error) => {\n console.warn('[autotel-edge] execution logger onEmit failed:', error);\n });\n }\n\n emitted = true;\n lastSnapshot = snapshot;\n return snapshot;\n },\n\n fork(\n label: string,\n fn: () => void | Promise<void>,\n forkOptions?: ForkOptions,\n ): void {\n const parentCorrelationId = activeContext.correlationId;\n if (\n typeof parentCorrelationId !== 'string' ||\n parentCorrelationId.length === 0\n ) {\n throw new Error(\n '[autotel-edge] log.fork() requires the parent logger to have a correlationId. ' +\n 'Ensure execution context was created by autotel trace instrumentation.',\n );\n }\n\n const lifecycle = forkOptions?.lifecycle;\n const tracer = otelTrace.getTracer('autotel-edge.execution-logger');\n void tracer.startActiveSpan(`execution.fork:${label}`, (childSpan) => {\n const childContext: TraceContext = {\n ...createTraceContext(childSpan),\n correlationId: generateCorrelationId(),\n };\n\n const childLog = getExecutionLogger(childContext);\n childLog.set({\n operation: label,\n _parentCorrelationId: parentCorrelationId,\n });\n\n lifecycle?.onChildEnter?.(childLog);\n\n return Promise.resolve()\n .then(() => fn())\n .then(() => {\n childLog.emitNow();\n })\n .catch((err: unknown) => {\n const error = err instanceof Error ? err : new Error(String(err));\n childLog.error(error);\n childLog.emitNow();\n })\n .finally(() => {\n try {\n lifecycle?.onChildExit?.(childLog);\n } catch (hookError) {\n console.warn(\n '[autotel-edge] fork onChildExit hook threw:',\n hookError,\n );\n }\n childSpan.end();\n });\n });\n },\n };\n}\n"],"mappings":"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"}
|