This commit is contained in:
Armando Guimarães Junior 2026-01-30 16:42:34 +01:00 committed by GitHub
commit 9f1de23bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 143 additions and 20 deletions

123
PR_EVIDENCE.md Normal file
View File

@ -0,0 +1,123 @@
# Fix Evidence: diagnostics-otel OpenTelemetry v2.x Compatibility
## Problem
The `diagnostics-otel` plugin fails to start due to API breaking changes in OpenTelemetry v2.x packages.
## Errors Before Fix
### Error 1: Resource constructor
```
[plugins] plugin service failed (diagnostics-otel): TypeError: _resources.Resource is not a constructor
```
**Root cause:** `@opentelemetry/resources@2.5.0` no longer exports `Resource` class.
### Error 2: addLogRecordProcessor method
```
[plugins] plugin service failed (diagnostics-otel): TypeError: logProvider.addLogRecordProcessor is not a function
```
**Root cause:** `@opentelemetry/sdk-logs@0.211.0` changed LoggerProvider API.
## Changes Made
### 1. Resource Creation (line 6, 65-67)
**Before:**
```typescript
import { Resource } from "@opentelemetry/resources";
// ...
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
});
```
**After:**
```typescript
import { resourceFromAttributes } from "@opentelemetry/resources";
// ...
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: serviceName,
});
```
### 2. Semantic Conventions (line 11)
**Before:**
```typescript
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
```
**After:**
```typescript
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
```
### 3. LoggerProvider Processors (line 202-210)
**Before:**
```typescript
logProvider = new LoggerProvider({ resource });
logProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(logExporter, { ... }),
);
```
**After:**
```typescript
const logProcessor = new BatchLogRecordProcessor(logExporter, { ... });
logProvider = new LoggerProvider({
resource,
processors: [logProcessor],
});
```
## Testing
### Environment
- **OS:** Linux (WSL2)
- **Node:** v22.22.0
- **Docker:** moltbot:local image
### Test Results
**Before fix:**
```
[plugins] plugin service failed (diagnostics-otel): TypeError: _resources.Resource is not a constructor
[plugins] plugin service failed (diagnostics-otel): TypeError: logProvider.addLogRecordProcessor is not a function
```
**After fix:**
```
[plugins] diagnostics-otel: logs exporter enabled (OTLP/HTTP)
```
✅ Plugin loads successfully
✅ Logs exporter enabled
✅ No errors in gateway startup
✅ Telemetry pipeline ready
### Validation Commands
```bash
# Build
pnpm build # ✅ No TypeScript errors
# Lint
npm run lint # ✅ 0 warnings, 0 errors
# Runtime test
docker logs moltbot-moltbot-gateway-1 | grep diagnostic
# Output: "diagnostics-otel: logs exporter enabled (OTLP/HTTP)"
```
## References
- OpenTelemetry JS v2.0 migration guide: https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/upgrade-to-2.x.md
- Issue #3201: diagnostics-otel plugin fails: Resource is not a constructor
- Related PR #2574: Partial fix (only addresses Resource, not LoggerProvider)
## AI Disclosure
This fix was developed with AI assistance (Claude Sonnet 4.5) and has been:
- ✅ Fully tested locally
- ✅ Validated with linter
- ✅ Verified in production-like environment (Docker)
- ✅ Understood by contributor (architectural reasoning documented)

View File

@ -65,7 +65,9 @@ vi.mock("@opentelemetry/exporter-logs-otlp-http", () => ({
vi.mock("@opentelemetry/sdk-logs", () => ({ vi.mock("@opentelemetry/sdk-logs", () => ({
BatchLogRecordProcessor: class {}, BatchLogRecordProcessor: class {},
LoggerProvider: class { LoggerProvider: class {
addLogRecordProcessor = vi.fn(); constructor(_options?: unknown) {
// v2.x: processors are passed in constructor
}
getLogger = vi.fn(() => ({ getLogger = vi.fn(() => ({
emit: logEmit, emit: logEmit,
})); }));
@ -83,16 +85,13 @@ vi.mock("@opentelemetry/sdk-trace-base", () => ({
})); }));
vi.mock("@opentelemetry/resources", () => ({ vi.mock("@opentelemetry/resources", () => ({
Resource: class { resourceFromAttributes: vi.fn((attributes?: unknown) => ({
// eslint-disable-next-line @typescript-eslint/no-useless-constructor attributes,
constructor(_value?: unknown) {} })),
},
})); }));
vi.mock("@opentelemetry/semantic-conventions", () => ({ vi.mock("@opentelemetry/semantic-conventions", () => ({
SemanticResourceAttributes: { ATTR_SERVICE_NAME: "service.name",
SERVICE_NAME: "service.name",
},
})); }));
vi.mock("openclaw/plugin-sdk", async () => { vi.mock("openclaw/plugin-sdk", async () => {

View File

@ -3,12 +3,12 @@ import type { SeverityNumber } from "@opentelemetry/api-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources"; import { resourceFromAttributes } from "@opentelemetry/resources";
import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs"; import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node"; import { NodeSDK } from "@opentelemetry/sdk-node";
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base"; import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import type { DiagnosticEventPayload, OpenClawPluginService } from "openclaw/plugin-sdk"; import type { DiagnosticEventPayload, OpenClawPluginService } from "openclaw/plugin-sdk";
import { onDiagnosticEvent, registerLogTransport } from "openclaw/plugin-sdk"; import { onDiagnosticEvent, registerLogTransport } from "openclaw/plugin-sdk";
@ -62,8 +62,8 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
const logsEnabled = otel.logs === true; const logsEnabled = otel.logs === true;
if (!tracesEnabled && !metricsEnabled && !logsEnabled) return; if (!tracesEnabled && !metricsEnabled && !logsEnabled) return;
const resource = new Resource({ const resource = resourceFromAttributes({
[SemanticResourceAttributes.SERVICE_NAME]: serviceName, [ATTR_SERVICE_NAME]: serviceName,
}); });
const traceUrl = resolveOtelUrl(endpoint, "v1/traces"); const traceUrl = resolveOtelUrl(endpoint, "v1/traces");
@ -199,14 +199,15 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
...(logUrl ? { url: logUrl } : {}), ...(logUrl ? { url: logUrl } : {}),
...(headers ? { headers } : {}), ...(headers ? { headers } : {}),
}); });
logProvider = new LoggerProvider({ resource }); const logProcessor = new BatchLogRecordProcessor(logExporter, {
logProvider.addLogRecordProcessor( ...(typeof otel.flushIntervalMs === "number"
new BatchLogRecordProcessor(logExporter, { ? { scheduledDelayMillis: Math.max(1000, otel.flushIntervalMs) }
...(typeof otel.flushIntervalMs === "number" : {}),
? { scheduledDelayMillis: Math.max(1000, otel.flushIntervalMs) } });
: {}), logProvider = new LoggerProvider({
}), resource,
); processors: [logProcessor],
});
const otelLogger = logProvider.getLogger("openclaw"); const otelLogger = logProvider.getLogger("openclaw");
stopLogTransport = registerLogTransport((logObj) => { stopLogTransport = registerLogTransport((logObj) => {