

























@@ -1,5 +1,11 @@
1-import { metrics, trace, SpanStatusCode } from "@opentelemetry/api";
2-import type { SeverityNumber } from "@opentelemetry/api-logs";
1+import {
2+context as otelContextApi,
3+metrics,
4+trace,
5+SpanStatusCode,
6+TraceFlags,
7+} from "@opentelemetry/api";
8+import type { LogRecord, SeverityNumber } from "@opentelemetry/api-logs";
39import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
410import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
511import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
@@ -9,8 +15,19 @@ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
915import { NodeSDK } from "@opentelemetry/sdk-node";
1016import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
1117import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
12-import type { DiagnosticEventPayload, OpenClawPluginService } from "../api.js";
13-import { onDiagnosticEvent, redactSensitiveText, registerLogTransport } from "../api.js";
18+import type {
19+DiagnosticEventPayload,
20+DiagnosticTraceContext,
21+OpenClawPluginService,
22+} from "../api.js";
23+import {
24+isValidDiagnosticSpanId,
25+isValidDiagnosticTraceFlags,
26+isValidDiagnosticTraceId,
27+onDiagnosticEvent,
28+redactSensitiveText,
29+registerLogTransport,
30+} from "../api.js";
14311532const DEFAULT_SERVICE_NAME = "openclaw";
1633@@ -62,6 +79,83 @@ function redactOtelAttributes(attributes: Record<string, string | number | boole
6279return redactedAttributes;
6380}
648182+function normalizeTraceContext(value: unknown): DiagnosticTraceContext | undefined {
83+if (!value || typeof value !== "object" || Array.isArray(value)) {
84+return undefined;
85+}
86+const candidate = value as Partial<DiagnosticTraceContext>;
87+if (!isValidDiagnosticTraceId(candidate.traceId)) {
88+return undefined;
89+}
90+if (candidate.spanId !== undefined && !isValidDiagnosticSpanId(candidate.spanId)) {
91+return undefined;
92+}
93+if (candidate.parentSpanId !== undefined && !isValidDiagnosticSpanId(candidate.parentSpanId)) {
94+return undefined;
95+}
96+if (candidate.traceFlags !== undefined && !isValidDiagnosticTraceFlags(candidate.traceFlags)) {
97+return undefined;
98+}
99+return {
100+traceId: candidate.traceId,
101+ ...(candidate.spanId ? { spanId: candidate.spanId } : {}),
102+ ...(candidate.parentSpanId ? { parentSpanId: candidate.parentSpanId } : {}),
103+ ...(candidate.traceFlags ? { traceFlags: candidate.traceFlags } : {}),
104+};
105+}
106+107+function extractTraceContext(value: unknown): DiagnosticTraceContext | undefined {
108+const direct = normalizeTraceContext(value);
109+if (direct) {
110+return direct;
111+}
112+if (!value || typeof value !== "object" || Array.isArray(value)) {
113+return undefined;
114+}
115+return normalizeTraceContext((value as { trace?: unknown }).trace);
116+}
117+118+function findLogTraceContext(
119+bindings: Record<string, unknown> | undefined,
120+numericArgs: unknown[],
121+): DiagnosticTraceContext | undefined {
122+const fromBindings = extractTraceContext(bindings);
123+if (fromBindings) {
124+return fromBindings;
125+}
126+for (const arg of numericArgs) {
127+const fromArg = extractTraceContext(arg);
128+if (fromArg) {
129+return fromArg;
130+}
131+}
132+return undefined;
133+}
134+135+function traceFlagsToOtel(traceFlags: string | undefined): TraceFlags {
136+const parsed = Number.parseInt(traceFlags ?? "00", 16);
137+return (parsed & TraceFlags.SAMPLED) !== 0 ? TraceFlags.SAMPLED : TraceFlags.NONE;
138+}
139+140+function addTraceAttributes(
141+attributes: Record<string, string | number | boolean>,
142+traceContext: DiagnosticTraceContext | undefined,
143+): void {
144+if (!traceContext) {
145+return;
146+}
147+attributes["openclaw.traceId"] = traceContext.traceId;
148+if (traceContext.spanId) {
149+attributes["openclaw.spanId"] = traceContext.spanId;
150+}
151+if (traceContext.parentSpanId) {
152+attributes["openclaw.parentSpanId"] = traceContext.parentSpanId;
153+}
154+if (traceContext.traceFlags) {
155+attributes["openclaw.traceFlags"] = traceContext.traceFlags;
156+}
157+}
158+65159export function createDiagnosticsOtelService(): OpenClawPluginService {
66160let sdk: NodeSDK | null = null;
67161let logProvider: LoggerProvider | null = null;
@@ -294,6 +388,7 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
294388// ignore malformed json bindings
295389}
296390}
391+const traceContext = findLogTraceContext(bindings, numericArgs);
297392298393let message = "";
299394if (numericArgs.length > 0 && typeof numericArgs[numericArgs.length - 1] === "string") {
@@ -343,15 +438,25 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
343438if (meta?.path?.filePathWithLine) {
344439attributes["openclaw.code.location"] = meta.path.filePathWithLine;
345440}
441+addTraceAttributes(attributes, traceContext);
346442347443// OTLP can leave the host boundary, so redact string fields before export.
348-otelLogger.emit({
444+const logRecord: LogRecord = {
349445body: redactSensitiveText(message),
350446severityText: logLevelName,
351447 severityNumber,
352448attributes: redactOtelAttributes(attributes),
353449timestamp: meta?.date ?? new Date(),
354-});
450+};
451+if (traceContext?.spanId) {
452+logRecord.context = trace.setSpanContext(otelContextApi.active(), {
453+traceId: traceContext.traceId,
454+spanId: traceContext.spanId,
455+traceFlags: traceFlagsToOtel(traceContext.traceFlags),
456+isRemote: true,
457+});
458+}
459+otelLogger.emit(logRecord);
355460} catch (err) {
356461ctx.logger.error(`diagnostics-otel: log transport failed: ${formatError(err)}`);
357462}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。