@openclaw/diagnostics-prometheus 2026.5.1-beta.2

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/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { createDiagnosticsPrometheusExporter } from "./src/service.js";
3
+
4
+ const exporter = createDiagnosticsPrometheusExporter();
5
+
6
+ export default definePluginEntry({
7
+ id: "diagnostics-prometheus",
8
+ name: "Diagnostics Prometheus",
9
+ description: "Expose OpenClaw diagnostics metrics in Prometheus text format",
10
+ register(api) {
11
+ api.registerService(exporter.service);
12
+ api.registerHttpRoute({
13
+ path: "/api/diagnostics/prometheus",
14
+ auth: "gateway",
15
+ match: "exact",
16
+ gatewayRuntimeScopeSurface: "trusted-operator",
17
+ handler: exporter.handler,
18
+ });
19
+ },
20
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "diagnostics-prometheus",
3
+ "activation": {
4
+ "onStartup": true
5
+ },
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {}
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@openclaw/diagnostics-prometheus",
3
+ "version": "2026.5.1-beta.2",
4
+ "description": "OpenClaw diagnostics Prometheus exporter",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/openclaw/openclaw"
8
+ },
9
+ "type": "module",
10
+ "devDependencies": {
11
+ "@openclaw/plugin-sdk": "workspace:*"
12
+ },
13
+ "openclaw": {
14
+ "extensions": [
15
+ "./index.ts"
16
+ ],
17
+ "compat": {
18
+ "pluginApi": ">=2026.4.25"
19
+ },
20
+ "build": {
21
+ "openclawVersion": "2026.5.1-beta.2"
22
+ },
23
+ "release": {
24
+ "publishToClawHub": true,
25
+ "publishToNpm": true
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,169 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { DiagnosticEventMetadata, DiagnosticEventPayload } from "../api.js";
3
+ import { createDiagnosticsPrometheusExporter, __test__ } from "./service.js";
4
+
5
+ const trusted: DiagnosticEventMetadata = Object.freeze({ trusted: true });
6
+ const untrusted: DiagnosticEventMetadata = Object.freeze({ trusted: false });
7
+
8
+ function baseEvent(): Pick<DiagnosticEventPayload, "seq" | "ts"> {
9
+ return { seq: 1, ts: 1700000000000 };
10
+ }
11
+
12
+ describe("diagnostics-prometheus service", () => {
13
+ it("records trusted run metrics without raw diagnostic identifiers", () => {
14
+ const store = __test__.createPrometheusMetricStore();
15
+
16
+ __test__.recordDiagnosticEvent(
17
+ store,
18
+ {
19
+ ...baseEvent(),
20
+ type: "run.completed",
21
+ runId: "run-should-not-export",
22
+ sessionKey: "session-should-not-export",
23
+ provider: "openai",
24
+ model: "gpt-5.4",
25
+ channel: "discord",
26
+ trigger: "message",
27
+ durationMs: 1500,
28
+ outcome: "completed",
29
+ },
30
+ trusted,
31
+ );
32
+
33
+ const rendered = __test__.renderPrometheusMetrics(store);
34
+
35
+ expect(rendered).toContain("# TYPE openclaw_run_completed_total counter");
36
+ expect(rendered).toContain(
37
+ 'openclaw_run_completed_total{channel="discord",model="gpt-5.4",outcome="completed",provider="openai",trigger="message"} 1',
38
+ );
39
+ expect(rendered).toContain(
40
+ 'openclaw_run_duration_seconds_sum{channel="discord",model="gpt-5.4",outcome="completed",provider="openai",trigger="message"} 1.5',
41
+ );
42
+ expect(rendered).not.toContain("run-should-not-export");
43
+ expect(rendered).not.toContain("session-should-not-export");
44
+ });
45
+
46
+ it("drops untrusted plugin-emitted diagnostic events", () => {
47
+ const store = __test__.createPrometheusMetricStore();
48
+
49
+ __test__.recordDiagnosticEvent(
50
+ store,
51
+ {
52
+ ...baseEvent(),
53
+ type: "model.call.completed",
54
+ runId: "run-1",
55
+ callId: "call-1",
56
+ provider: "openai",
57
+ model: "gpt-5.4",
58
+ durationMs: 10,
59
+ },
60
+ untrusted,
61
+ );
62
+
63
+ expect(__test__.renderPrometheusMetrics(store)).toBe("");
64
+ });
65
+
66
+ it("redacts and bounds label values", () => {
67
+ const store = __test__.createPrometheusMetricStore();
68
+
69
+ __test__.recordDiagnosticEvent(
70
+ store,
71
+ {
72
+ ...baseEvent(),
73
+ type: "tool.execution.error",
74
+ toolName: "shell\nbad",
75
+ durationMs: 25,
76
+ errorCategory: "Bearer sk-secret-token-value",
77
+ },
78
+ trusted,
79
+ );
80
+
81
+ const rendered = __test__.renderPrometheusMetrics(store);
82
+
83
+ expect(rendered).toContain(
84
+ 'openclaw_tool_execution_total{error_category="other",outcome="error",params_kind="unknown",tool="tool"} 1',
85
+ );
86
+ expect(rendered).not.toContain("Bearer");
87
+ expect(rendered).not.toContain("sk-secret");
88
+ });
89
+
90
+ it("caps metric series growth and reports dropped series", () => {
91
+ const store = __test__.createPrometheusMetricStore();
92
+
93
+ for (let index = 0; index < 2100; index += 1) {
94
+ __test__.recordDiagnosticEvent(
95
+ store,
96
+ {
97
+ ...baseEvent(),
98
+ type: "model.call.completed",
99
+ runId: `run-${index}`,
100
+ callId: `call-${index}`,
101
+ provider: "openai",
102
+ model: `model.${index}`,
103
+ durationMs: 10,
104
+ },
105
+ trusted,
106
+ );
107
+ }
108
+
109
+ const rendered = __test__.renderPrometheusMetrics(store);
110
+
111
+ expect(rendered).toContain("# TYPE openclaw_prometheus_series_dropped_total counter");
112
+ expect(rendered).toContain("openclaw_prometheus_series_dropped_total ");
113
+ });
114
+
115
+ it("subscribes to internal diagnostics and renders scrape text", () => {
116
+ const listeners: Array<
117
+ (event: DiagnosticEventPayload, metadata: DiagnosticEventMetadata) => void
118
+ > = [];
119
+ const emitted: unknown[] = [];
120
+ const exporter = createDiagnosticsPrometheusExporter();
121
+ const unsubscribe = vi.fn();
122
+
123
+ exporter.service.start({
124
+ config: {} as never,
125
+ stateDir: "/tmp/openclaw-prometheus-test",
126
+ logger: {
127
+ info: vi.fn(),
128
+ warn: vi.fn(),
129
+ error: vi.fn(),
130
+ debug: vi.fn(),
131
+ },
132
+ internalDiagnostics: {
133
+ emit: (event) => emitted.push(event),
134
+ onEvent: (listener) => {
135
+ listeners.push(listener);
136
+ return unsubscribe;
137
+ },
138
+ },
139
+ });
140
+
141
+ listeners[0]?.(
142
+ {
143
+ ...baseEvent(),
144
+ type: "model.usage",
145
+ provider: "openai",
146
+ model: "gpt-5.4",
147
+ usage: { input: 12, output: 3, total: 15 },
148
+ },
149
+ trusted,
150
+ );
151
+
152
+ expect(emitted).toContainEqual(
153
+ expect.objectContaining({
154
+ type: "telemetry.exporter",
155
+ exporter: "diagnostics-prometheus",
156
+ signal: "metrics",
157
+ status: "started",
158
+ }),
159
+ );
160
+ expect(exporter.render()).toContain(
161
+ 'openclaw_model_tokens_total{agent="unknown",channel="unknown",model="gpt-5.4",provider="openai",token_type="input"} 12',
162
+ );
163
+
164
+ exporter.service.stop?.();
165
+
166
+ expect(unsubscribe).toHaveBeenCalledOnce();
167
+ expect(exporter.render()).toBe("");
168
+ });
169
+ });