Skip to content

Commit 8d22eb2

Browse files
Merge pull request #31 from MichaelRuhwedel/fix/respect-metrics-temporality-preference
2 parents f79aa8e + f3f51c4 commit 8d22eb2

3 files changed

Lines changed: 75 additions & 4 deletions

File tree

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ All configuration is via environment variables. Set them in your shell profile (
9090
| `OPENCODE_DISABLE_METRICS` | _(unset)_ | Comma-separated list of metric name suffixes to disable (e.g. `cache.count,session.duration`) |
9191
| `OPENCODE_OTLP_HEADERS` | _(unset)_ | Comma-separated `key=value` headers added to all OTLP exports. **Keep out of version control — may contain sensitive auth tokens.** |
9292
| `OPENCODE_RESOURCE_ATTRIBUTES` | _(unset)_ | Comma-separated `key=value` pairs merged into the OTel resource. Example: `service.version=1.2.3,deployment.environment=production` |
93+
| `OPENCODE_OTLP_METRICS_TEMPORALITY` | _(unset)_ | Metrics aggregation temporality: `delta`, `cumulative`, or `lowmemory`. Required for Datadog (`delta`). Copied to `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`. |
9394

9495
### Quick start
9596

@@ -150,10 +151,18 @@ export OPENCODE_DISABLE_METRICS="cache.count,session.duration,session.token.tota
150151

151152
```bash
152153
export OPENCODE_ENABLE_TELEMETRY=1
153-
export OPENCODE_OTLP_ENDPOINT=https://api.datadoghq.com
154+
export OPENCODE_OTLP_ENDPOINT=https://otlp.datadoghq.com
154155
export OPENCODE_OTLP_PROTOCOL=http/protobuf
156+
export OPENCODE_OTLP_HEADERS="dd-api-key=YOUR_DATADOG_API_KEY"
157+
158+
# Required — Datadog's OTLP intake only accepts delta temporality
159+
export OPENCODE_OTLP_METRICS_TEMPORALITY=delta
155160
```
156161

162+
> **Note:** The endpoint is `otlp.datadoghq.com` (not `api.datadoghq.com`).
163+
> Use `otlp.datadoghq.eu` for EU, `otlp.us3.datadoghq.com` for US3, etc.
164+
> See [Datadog OTLP docs](https://docs.datadoghq.com/opentelemetry/setup/otlp_ingest_in_the_agent/) for all regions.
165+
157166
### Honeycomb example
158167

159168
```bash

src/config.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { LEVELS, type Level } from "./types.ts"
22

3+
/** Accepted values for `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`. */
4+
export type MetricsTemporality = "cumulative" | "delta" | "lowmemory"
5+
6+
const VALID_TEMPORALITIES: ReadonlySet<MetricsTemporality> = new Set<MetricsTemporality>(["cumulative", "delta", "lowmemory"])
7+
38
/** Configuration values resolved from `OPENCODE_*` environment variables. */
49
export type PluginConfig = {
510
enabled: boolean
@@ -10,6 +15,7 @@ export type PluginConfig = {
1015
metricPrefix: string
1116
otlpHeaders: string | undefined
1217
resourceAttributes: string | undefined
18+
metricsTemporality: MetricsTemporality | undefined
1319
disabledMetrics: Set<string>
1420
disabledTraces: Set<string>
1521
}
@@ -25,15 +31,31 @@ export function parseEnvInt(key: string, fallback: number): number {
2531

2632
/**
2733
* Reads all `OPENCODE_*` environment variables and returns the resolved plugin config.
28-
* Copies `OPENCODE_OTLP_HEADERS` → `OTEL_EXPORTER_OTLP_HEADERS` and
29-
* `OPENCODE_RESOURCE_ATTRIBUTES` → `OTEL_RESOURCE_ATTRIBUTES` so the OTel SDK
30-
* picks them up automatically when initialised.
34+
* Copies `OPENCODE_OTLP_HEADERS` → `OTEL_EXPORTER_OTLP_HEADERS`,
35+
* `OPENCODE_RESOURCE_ATTRIBUTES` → `OTEL_RESOURCE_ATTRIBUTES`, and
36+
* `OPENCODE_OTLP_METRICS_TEMPORALITY` → `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`
37+
* so the OTel SDK picks them up automatically when initialised.
3138
*/
3239
export function loadConfig(): PluginConfig {
3340
const otlpHeaders = process.env["OPENCODE_OTLP_HEADERS"]
3441
const resourceAttributes = process.env["OPENCODE_RESOURCE_ATTRIBUTES"]
42+
const rawTemporality = process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"]
3543
const protocol = process.env["OPENCODE_OTLP_PROTOCOL"]
3644

45+
let metricsTemporality: MetricsTemporality | undefined
46+
if (rawTemporality) {
47+
const normalized = rawTemporality.toLowerCase()
48+
if (VALID_TEMPORALITIES.has(normalized as MetricsTemporality)) {
49+
metricsTemporality = normalized as MetricsTemporality
50+
process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"] = normalized
51+
} else {
52+
console.warn(
53+
`[opencode-plugin-otel] Invalid OPENCODE_OTLP_METRICS_TEMPORALITY="${rawTemporality}". ` +
54+
`Expected one of: cumulative, delta, lowmemory. Value ignored.`,
55+
)
56+
}
57+
}
58+
3759
if (otlpHeaders) process.env["OTEL_EXPORTER_OTLP_HEADERS"] = otlpHeaders
3860
if (resourceAttributes) process.env["OTEL_RESOURCE_ATTRIBUTES"] = resourceAttributes
3961

@@ -60,6 +82,7 @@ export function loadConfig(): PluginConfig {
6082
metricPrefix: process.env["OPENCODE_METRIC_PREFIX"] ?? "opencode.",
6183
otlpHeaders,
6284
resourceAttributes,
85+
metricsTemporality,
6386
disabledMetrics,
6487
disabledTraces,
6588
}

tests/config.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ describe("loadConfig", () => {
4949
"OPENCODE_OTLP_LOGS_INTERVAL",
5050
"OPENCODE_OTLP_HEADERS",
5151
"OPENCODE_RESOURCE_ATTRIBUTES",
52+
"OPENCODE_OTLP_METRICS_TEMPORALITY",
5253
"OPENCODE_DISABLE_METRICS",
5354
"OPENCODE_DISABLE_TRACES",
5455
"OTEL_EXPORTER_OTLP_HEADERS",
5556
"OTEL_RESOURCE_ATTRIBUTES",
57+
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE",
5658
]
5759
beforeEach(() => vars.forEach((k) => delete process.env[k]))
5860
afterEach(() => vars.forEach((k) => delete process.env[k]))
@@ -120,6 +122,43 @@ describe("loadConfig", () => {
120122
expect(process.env["OTEL_EXPORTER_OTLP_HEADERS"]).toBeUndefined()
121123
})
122124

125+
test("copies OPENCODE_OTLP_METRICS_TEMPORALITY to OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE", () => {
126+
process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"] = "delta"
127+
const cfg = loadConfig()
128+
expect(process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"]).toBe("delta")
129+
expect(cfg.metricsTemporality).toBe("delta")
130+
})
131+
132+
test("does not set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE when OPENCODE_OTLP_METRICS_TEMPORALITY is unset", () => {
133+
delete process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"]
134+
const cfg = loadConfig()
135+
expect(process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"]).toBeUndefined()
136+
expect(cfg.metricsTemporality).toBeUndefined()
137+
})
138+
139+
test("normalizes OPENCODE_OTLP_METRICS_TEMPORALITY to lowercase", () => {
140+
process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"] = "Delta"
141+
const cfg = loadConfig()
142+
expect(process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"]).toBe("delta")
143+
expect(cfg.metricsTemporality).toBe("delta")
144+
})
145+
146+
test("ignores invalid OPENCODE_OTLP_METRICS_TEMPORALITY and warns", () => {
147+
const warnings: string[] = []
148+
const origWarn = console.warn
149+
console.warn = (...args: unknown[]) => warnings.push(String(args[0]))
150+
try {
151+
process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"] = "bogus"
152+
const cfg = loadConfig()
153+
expect(process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"]).toBeUndefined()
154+
expect(cfg.metricsTemporality).toBeUndefined()
155+
expect(warnings.length).toBe(1)
156+
expect(warnings[0]).toContain("bogus")
157+
} finally {
158+
console.warn = origWarn
159+
}
160+
})
161+
123162
test("does not overwrite pre-existing OTEL_* vars when OPENCODE_* vars are unset", () => {
124163
process.env["OTEL_EXPORTER_OTLP_HEADERS"] = "existing-header=value"
125164
process.env["OTEL_RESOURCE_ATTRIBUTES"] = "existing=attr"

0 commit comments

Comments
 (0)