@openclaw/diagnostics-otel 2026.6.9-beta.1 → 2026.6.10-beta.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 +1 -1
- package/dist/index.js +125 -69
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Official OpenTelemetry diagnostics exporter for OpenClaw.
|
|
4
4
|
|
|
5
|
-
This plugin exports OpenClaw Gateway traces, metrics, and logs to an OTLP collector for observability stacks such as Grafana, Datadog, Honeycomb, New Relic, Tempo, and compatible collectors.
|
|
5
|
+
This plugin exports OpenClaw Gateway traces, metrics, and logs to an OTLP collector for observability stacks such as Grafana, Datadog, Honeycomb, New Relic, Tempo, and compatible collectors. It can also write diagnostic log records as stdout JSONL for container log pipelines.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
package/dist/index.js
CHANGED
|
@@ -215,6 +215,31 @@ function lowCardinalityQueueLaneAttr(value, fallback = "unknown") {
|
|
|
215
215
|
function shouldCaptureOtelLogBody(policy) {
|
|
216
216
|
return policy.logBodies;
|
|
217
217
|
}
|
|
218
|
+
function otelLogTimestampIso(timestamp) {
|
|
219
|
+
if (timestamp instanceof Date) return timestamp.toISOString();
|
|
220
|
+
if (typeof timestamp === "number" && Number.isFinite(timestamp)) return new Date(timestamp).toISOString();
|
|
221
|
+
if (Array.isArray(timestamp)) {
|
|
222
|
+
const [seconds, nanoseconds] = timestamp;
|
|
223
|
+
if (Number.isFinite(seconds) && Number.isFinite(nanoseconds)) return new Date(seconds * 1e3 + Math.trunc(nanoseconds / 1e6)).toISOString();
|
|
224
|
+
}
|
|
225
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
226
|
+
}
|
|
227
|
+
function writeStdoutDiagnosticLogRecord(params) {
|
|
228
|
+
const { logRecord, serviceName, traceContext } = params;
|
|
229
|
+
const line = {
|
|
230
|
+
ts: otelLogTimestampIso(logRecord.timestamp),
|
|
231
|
+
signal: "openclaw.diagnostic.log",
|
|
232
|
+
"service.name": serviceName,
|
|
233
|
+
severityText: logRecord.severityText,
|
|
234
|
+
severityNumber: logRecord.severityNumber,
|
|
235
|
+
body: logRecord.body,
|
|
236
|
+
attributes: logRecord.attributes ?? {},
|
|
237
|
+
...traceContext?.traceId ? { trace_id: traceContext.traceId } : {},
|
|
238
|
+
...traceContext?.spanId ? { span_id: traceContext.spanId } : {},
|
|
239
|
+
...traceContext?.traceFlags ? { trace_flags: traceContext.traceFlags } : {}
|
|
240
|
+
};
|
|
241
|
+
process.stdout.write(`${JSON.stringify(line)}\n`);
|
|
242
|
+
}
|
|
218
243
|
function hasOtelSemconvOptIn(value, optIn) {
|
|
219
244
|
return value?.split(",").map((part) => part.trim()).includes(optIn) ?? false;
|
|
220
245
|
}
|
|
@@ -688,6 +713,9 @@ function contextForTraceContext(traceContext) {
|
|
|
688
713
|
function contextForTrustedTraceContext(evt, metadata) {
|
|
689
714
|
return metadata.trusted || metadata.trustedTraceContext === true ? contextForTraceContext(evt.trace) : void 0;
|
|
690
715
|
}
|
|
716
|
+
function normalizedTrustedTraceContext(evt, metadata) {
|
|
717
|
+
return metadata.trusted || metadata.trustedTraceContext === true ? normalizeTraceContext(evt.trace) : void 0;
|
|
718
|
+
}
|
|
691
719
|
function addTraceAttributes(attributes, traceContext) {
|
|
692
720
|
const normalized = normalizeTraceContext(traceContext);
|
|
693
721
|
if (!normalized) return;
|
|
@@ -743,6 +771,14 @@ function createDiagnosticsOtelService() {
|
|
|
743
771
|
const tracesEnabled = otel.traces !== false;
|
|
744
772
|
const metricsEnabled = otel.metrics !== false;
|
|
745
773
|
const logsEnabled = otel.logs === true;
|
|
774
|
+
const logsExporter = otel.logsExporter ?? "otlp";
|
|
775
|
+
const logsToOtlp = logsEnabled && (logsExporter === "otlp" || logsExporter === "both");
|
|
776
|
+
const logsToStdout = logsEnabled && (logsExporter === "stdout" || logsExporter === "both");
|
|
777
|
+
const otlpSignals = [
|
|
778
|
+
...tracesEnabled ? ["traces"] : [],
|
|
779
|
+
...metricsEnabled ? ["metrics"] : [],
|
|
780
|
+
...logsToOtlp ? ["logs"] : []
|
|
781
|
+
];
|
|
746
782
|
const enabledSignals = [
|
|
747
783
|
...tracesEnabled ? ["traces"] : [],
|
|
748
784
|
...metricsEnabled ? ["metrics"] : [],
|
|
@@ -750,8 +786,8 @@ function createDiagnosticsOtelService() {
|
|
|
750
786
|
];
|
|
751
787
|
if (enabledSignals.length === 0) return;
|
|
752
788
|
const protocol = otel.protocol ?? process.env.OTEL_EXPORTER_OTLP_PROTOCOL ?? "http/protobuf";
|
|
753
|
-
if (protocol !== "http/protobuf") {
|
|
754
|
-
emitForSignals(
|
|
789
|
+
if (otlpSignals.length > 0 && protocol !== "http/protobuf") {
|
|
790
|
+
emitForSignals(otlpSignals, {
|
|
755
791
|
exporter: "diagnostics-otel",
|
|
756
792
|
status: "failure",
|
|
757
793
|
reason: "unsupported_protocol"
|
|
@@ -1083,81 +1119,98 @@ function createDiagnosticsOtelService() {
|
|
|
1083
1119
|
let recordSecurityEvent;
|
|
1084
1120
|
if (logsEnabled) {
|
|
1085
1121
|
let logRecordExportFailureLastReportedAt = Number.NEGATIVE_INFINITY;
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1122
|
+
let otelLogger;
|
|
1123
|
+
if (logsToOtlp) {
|
|
1124
|
+
logProvider = new LoggerProvider({
|
|
1125
|
+
resource,
|
|
1126
|
+
processors: [new BatchLogRecordProcessor(new OTLPLogExporter({
|
|
1127
|
+
...logUrl ? { url: logUrl } : {},
|
|
1128
|
+
...headers ? { headers } : {}
|
|
1129
|
+
}), typeof otel.flushIntervalMs === "number" ? { scheduledDelayMillis: Math.max(1e3, otel.flushIntervalMs) } : {})]
|
|
1130
|
+
});
|
|
1131
|
+
otelLogger = logProvider.getLogger("openclaw");
|
|
1132
|
+
}
|
|
1133
|
+
const reportLogExportFailure = (err, label) => {
|
|
1134
|
+
emitExporterEvent({
|
|
1135
|
+
exporter: "diagnostics-otel",
|
|
1136
|
+
signal: "logs",
|
|
1137
|
+
status: "failure",
|
|
1138
|
+
reason: "emit_failed",
|
|
1139
|
+
errorCategory: errorCategory(err)
|
|
1140
|
+
});
|
|
1141
|
+
const now = Date.now();
|
|
1142
|
+
if (now - logRecordExportFailureLastReportedAt >= LOG_RECORD_EXPORT_FAILURE_REPORT_INTERVAL_MS) {
|
|
1143
|
+
logRecordExportFailureLastReportedAt = now;
|
|
1144
|
+
ctx.logger.error(`diagnostics-otel: ${label} export failed: ${formatError(err)}`);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const emitLogRecord = ({ logRecord, traceContext }) => {
|
|
1148
|
+
if (logsToOtlp) otelLogger?.emit(logRecord);
|
|
1149
|
+
if (logsToStdout) writeStdoutDiagnosticLogRecord({
|
|
1150
|
+
logRecord,
|
|
1151
|
+
serviceName,
|
|
1152
|
+
...traceContext ? { traceContext } : {}
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
const buildDiagnosticLogRecord = (evt, metadata) => {
|
|
1156
|
+
const logLevelName = evt.level || "INFO";
|
|
1157
|
+
const severityNumber = logSeverityMap[logLevelName] ?? 9;
|
|
1158
|
+
const body = shouldCaptureOtelLogBody(contentCapturePolicy) ? normalizeOtelLogString(evt.message || "log", MAX_OTEL_LOG_BODY_CHARS) : "log";
|
|
1159
|
+
const attributes = Object.create(null);
|
|
1160
|
+
assignOtelLogAttribute(attributes, "openclaw.log.level", logLevelName);
|
|
1161
|
+
if (evt.loggerName) assignOtelLogAttribute(attributes, "openclaw.logger", evt.loggerName);
|
|
1162
|
+
if (evt.loggerParents?.length) assignOtelLogAttribute(attributes, "openclaw.logger.parents", evt.loggerParents.join("."));
|
|
1163
|
+
assignOtelLogEventAttributes(attributes, evt.attributes);
|
|
1164
|
+
if (evt.code?.line) assignOtelLogAttribute(attributes, "code.lineno", evt.code.line);
|
|
1165
|
+
if (evt.code?.functionName) assignOtelLogAttribute(attributes, "code.function", evt.code.functionName);
|
|
1166
|
+
const traceContext = normalizedTrustedTraceContext(evt, metadata);
|
|
1167
|
+
addTraceAttributes(attributes, traceContext);
|
|
1168
|
+
const logRecord = {
|
|
1169
|
+
body,
|
|
1170
|
+
severityText: logLevelName,
|
|
1171
|
+
severityNumber,
|
|
1172
|
+
attributes: redactOtelAttributes(attributes),
|
|
1173
|
+
timestamp: evt.ts
|
|
1174
|
+
};
|
|
1175
|
+
const logContext = contextForTrustedTraceContext(evt, metadata);
|
|
1176
|
+
if (logContext) logRecord.context = logContext;
|
|
1177
|
+
return {
|
|
1178
|
+
logRecord,
|
|
1179
|
+
...traceContext ? { traceContext } : {}
|
|
1180
|
+
};
|
|
1181
|
+
};
|
|
1182
|
+
const buildSecurityLogRecord = (evt, metadata) => {
|
|
1183
|
+
const severityText = securitySeverityText(evt.severity);
|
|
1184
|
+
const attributes = Object.create(null);
|
|
1185
|
+
assignOtelSecurityAttributes(attributes, evt);
|
|
1186
|
+
const traceContext = normalizedTrustedTraceContext(evt, metadata);
|
|
1187
|
+
const logRecord = {
|
|
1188
|
+
body: "openclaw.security.event",
|
|
1189
|
+
severityText,
|
|
1190
|
+
severityNumber: logSeverityMap[severityText] ?? 9,
|
|
1191
|
+
attributes: redactOtelAttributes(attributes),
|
|
1192
|
+
timestamp: evt.ts
|
|
1193
|
+
};
|
|
1194
|
+
const logContext = contextForTrustedTraceContext(evt, metadata);
|
|
1195
|
+
if (logContext) logRecord.context = logContext;
|
|
1196
|
+
return {
|
|
1197
|
+
logRecord,
|
|
1198
|
+
...traceContext ? { traceContext } : {}
|
|
1199
|
+
};
|
|
1200
|
+
};
|
|
1094
1201
|
recordLogRecord = (evt, metadata) => {
|
|
1095
1202
|
try {
|
|
1096
|
-
|
|
1097
|
-
const severityNumber = logSeverityMap[logLevelName] ?? 9;
|
|
1098
|
-
const body = shouldCaptureOtelLogBody(contentCapturePolicy) ? normalizeOtelLogString(evt.message || "log", MAX_OTEL_LOG_BODY_CHARS) : "log";
|
|
1099
|
-
const attributes = Object.create(null);
|
|
1100
|
-
assignOtelLogAttribute(attributes, "openclaw.log.level", logLevelName);
|
|
1101
|
-
if (evt.loggerName) assignOtelLogAttribute(attributes, "openclaw.logger", evt.loggerName);
|
|
1102
|
-
if (evt.loggerParents?.length) assignOtelLogAttribute(attributes, "openclaw.logger.parents", evt.loggerParents.join("."));
|
|
1103
|
-
assignOtelLogEventAttributes(attributes, evt.attributes);
|
|
1104
|
-
if (evt.code?.line) assignOtelLogAttribute(attributes, "code.lineno", evt.code.line);
|
|
1105
|
-
if (evt.code?.functionName) assignOtelLogAttribute(attributes, "code.function", evt.code.functionName);
|
|
1106
|
-
if (metadata.trusted || metadata.trustedTraceContext === true) addTraceAttributes(attributes, evt.trace);
|
|
1107
|
-
const logRecord = {
|
|
1108
|
-
body,
|
|
1109
|
-
severityText: logLevelName,
|
|
1110
|
-
severityNumber,
|
|
1111
|
-
attributes: redactOtelAttributes(attributes),
|
|
1112
|
-
timestamp: evt.ts
|
|
1113
|
-
};
|
|
1114
|
-
const logContext = contextForTrustedTraceContext(evt, metadata);
|
|
1115
|
-
if (logContext) logRecord.context = logContext;
|
|
1116
|
-
otelLogger.emit(logRecord);
|
|
1203
|
+
emitLogRecord(buildDiagnosticLogRecord(evt, metadata));
|
|
1117
1204
|
} catch (err) {
|
|
1118
|
-
|
|
1119
|
-
exporter: "diagnostics-otel",
|
|
1120
|
-
signal: "logs",
|
|
1121
|
-
status: "failure",
|
|
1122
|
-
reason: "emit_failed",
|
|
1123
|
-
errorCategory: errorCategory(err)
|
|
1124
|
-
});
|
|
1125
|
-
const now = Date.now();
|
|
1126
|
-
if (now - logRecordExportFailureLastReportedAt >= LOG_RECORD_EXPORT_FAILURE_REPORT_INTERVAL_MS) {
|
|
1127
|
-
logRecordExportFailureLastReportedAt = now;
|
|
1128
|
-
ctx.logger.error(`diagnostics-otel: log record export failed: ${formatError(err)}`);
|
|
1129
|
-
}
|
|
1205
|
+
reportLogExportFailure(err, "log record");
|
|
1130
1206
|
}
|
|
1131
1207
|
};
|
|
1132
1208
|
recordSecurityEvent = (evt, metadata) => {
|
|
1133
1209
|
if (!metadata.trusted) return;
|
|
1134
1210
|
try {
|
|
1135
|
-
|
|
1136
|
-
const attributes = Object.create(null);
|
|
1137
|
-
assignOtelSecurityAttributes(attributes, evt);
|
|
1138
|
-
const logRecord = {
|
|
1139
|
-
body: "openclaw.security.event",
|
|
1140
|
-
severityText,
|
|
1141
|
-
severityNumber: logSeverityMap[severityText] ?? 9,
|
|
1142
|
-
attributes: redactOtelAttributes(attributes),
|
|
1143
|
-
timestamp: evt.ts
|
|
1144
|
-
};
|
|
1145
|
-
const logContext = contextForTrustedTraceContext(evt, metadata);
|
|
1146
|
-
if (logContext) logRecord.context = logContext;
|
|
1147
|
-
otelLogger.emit(logRecord);
|
|
1211
|
+
emitLogRecord(buildSecurityLogRecord(evt, metadata));
|
|
1148
1212
|
} catch (err) {
|
|
1149
|
-
|
|
1150
|
-
exporter: "diagnostics-otel",
|
|
1151
|
-
signal: "logs",
|
|
1152
|
-
status: "failure",
|
|
1153
|
-
reason: "emit_failed",
|
|
1154
|
-
errorCategory: errorCategory(err)
|
|
1155
|
-
});
|
|
1156
|
-
const now = Date.now();
|
|
1157
|
-
if (now - logRecordExportFailureLastReportedAt >= LOG_RECORD_EXPORT_FAILURE_REPORT_INTERVAL_MS) {
|
|
1158
|
-
logRecordExportFailureLastReportedAt = now;
|
|
1159
|
-
ctx.logger.error(`diagnostics-otel: security event export failed: ${formatError(err)}`);
|
|
1160
|
-
}
|
|
1213
|
+
reportLogExportFailure(err, "security event");
|
|
1161
1214
|
}
|
|
1162
1215
|
};
|
|
1163
1216
|
}
|
|
@@ -2294,7 +2347,10 @@ function createDiagnosticsOtelService() {
|
|
|
2294
2347
|
status: "started",
|
|
2295
2348
|
reason: "configured"
|
|
2296
2349
|
});
|
|
2297
|
-
if (logsEnabled)
|
|
2350
|
+
if (logsEnabled) {
|
|
2351
|
+
const label = logsExporter === "both" ? "OTLP/Protobuf + stdout JSONL" : logsExporter === "stdout" ? "stdout JSONL" : "OTLP/Protobuf";
|
|
2352
|
+
ctx.logger.info(`diagnostics-otel: logs exporter enabled (${label})`);
|
|
2353
|
+
}
|
|
2298
2354
|
},
|
|
2299
2355
|
async stop() {
|
|
2300
2356
|
await stopStarted();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/diagnostics-otel",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.10-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/diagnostics-otel",
|
|
9
|
-
"version": "2026.6.
|
|
9
|
+
"version": "2026.6.10-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@opentelemetry/api": "1.9.1",
|
|
12
12
|
"@opentelemetry/api-logs": "0.219.0",
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "diagnostics-otel",
|
|
3
3
|
"name": "Diagnostics OpenTelemetry",
|
|
4
|
-
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics and
|
|
4
|
+
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics, traces, and logs.",
|
|
5
5
|
"activation": {
|
|
6
6
|
"onStartup": true
|
|
7
7
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/diagnostics-otel",
|
|
3
|
-
"version": "2026.6.
|
|
4
|
-
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics and
|
|
3
|
+
"version": "2026.6.10-beta.1",
|
|
4
|
+
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics, traces, and logs.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/openclaw/openclaw"
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"minHostVersion": ">=2026.4.25"
|
|
32
32
|
},
|
|
33
33
|
"compat": {
|
|
34
|
-
"pluginApi": ">=2026.6.
|
|
34
|
+
"pluginApi": ">=2026.6.10-beta.1"
|
|
35
35
|
},
|
|
36
36
|
"build": {
|
|
37
|
-
"openclawVersion": "2026.6.
|
|
37
|
+
"openclawVersion": "2026.6.10-beta.1"
|
|
38
38
|
},
|
|
39
39
|
"release": {
|
|
40
40
|
"publishToClawHub": true,
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"README.md"
|
|
52
52
|
],
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"openclaw": ">=2026.6.
|
|
54
|
+
"openclaw": ">=2026.6.10-beta.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"openclaw": {
|