summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/control-plane/workspace.ts1
-rw-r--r--packages/opencode/src/effect/observability.ts19
-rw-r--r--packages/opencode/test/effect/observability.test.ts45
3 files changed, 64 insertions, 1 deletions
diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts
index e94d6c2c9..eb689df02 100644
--- a/packages/opencode/src/control-plane/workspace.ts
+++ b/packages/opencode/src/control-plane/workspace.ts
@@ -117,6 +117,7 @@ export const create = fn(CreateInput, async (input) => {
OPENCODE_EXPERIMENTAL_WORKSPACES: "true",
OTEL_EXPORTER_OTLP_HEADERS: process.env.OTEL_EXPORTER_OTLP_HEADERS,
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
+ OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES,
}
await adaptor.create(config, env)
diff --git a/packages/opencode/src/effect/observability.ts b/packages/opencode/src/effect/observability.ts
index 1c385d60a..fd719fd35 100644
--- a/packages/opencode/src/effect/observability.ts
+++ b/packages/opencode/src/effect/observability.ts
@@ -21,12 +21,29 @@ const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
)
: undefined
-function resource() {
+export function resource(): { serviceName: string, serviceVersion: string, attributes: Record<string, string> } {
const processMetadata = ensureProcessMetadata("main")
+ const attributes: Record<string, string> = (() => {
+ const value = process.env.OTEL_RESOURCE_ATTRIBUTES
+ if (!value) return {}
+ try {
+ return Object.fromEntries(
+ value.split(",").map((entry) => {
+ const index = entry.indexOf("=")
+ if (index < 1) throw new Error("Invalid OTEL_RESOURCE_ATTRIBUTES entry")
+ return [decodeURIComponent(entry.slice(0, index)), decodeURIComponent(entry.slice(index + 1))]
+ }),
+ )
+ } catch {
+ return {}
+ }
+ })()
+
return {
serviceName: "opencode",
serviceVersion: InstallationVersion,
attributes: {
+ ...attributes,
"deployment.environment.name": InstallationChannel,
"opencode.client": Flag.OPENCODE_CLIENT,
"opencode.process_role": processMetadata.processRole,
diff --git a/packages/opencode/test/effect/observability.test.ts b/packages/opencode/test/effect/observability.test.ts
new file mode 100644
index 000000000..dd380a2de
--- /dev/null
+++ b/packages/opencode/test/effect/observability.test.ts
@@ -0,0 +1,45 @@
+import { afterEach, describe, expect, test } from "bun:test"
+import { resource } from "../../src/effect/observability"
+
+const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES
+const opencodeClient = process.env.OPENCODE_CLIENT
+
+afterEach(() => {
+ if (otelResourceAttributes === undefined) delete process.env.OTEL_RESOURCE_ATTRIBUTES
+ else process.env.OTEL_RESOURCE_ATTRIBUTES = otelResourceAttributes
+
+ if (opencodeClient === undefined) delete process.env.OPENCODE_CLIENT
+ else process.env.OPENCODE_CLIENT = opencodeClient
+})
+
+describe("resource", () => {
+ test("parses and decodes OTEL resource attributes", () => {
+ process.env.OTEL_RESOURCE_ATTRIBUTES =
+ "service.namespace=anomalyco,team=platform%2Cobservability,label=hello%3Dworld,key%2Fname=value%20here"
+
+ expect(resource().attributes).toMatchObject({
+ "service.namespace": "anomalyco",
+ team: "platform,observability",
+ label: "hello=world",
+ "key/name": "value here",
+ })
+ })
+
+ test("drops OTEL resource attributes when any entry is invalid", () => {
+ process.env.OTEL_RESOURCE_ATTRIBUTES = "service.namespace=anomalyco,broken"
+
+ expect(resource().attributes["service.namespace"]).toBeUndefined()
+ expect(resource().attributes["opencode.client"]).toBeDefined()
+ })
+
+ test("keeps built-in attributes when env values conflict", () => {
+ process.env.OPENCODE_CLIENT = "cli"
+ process.env.OTEL_RESOURCE_ATTRIBUTES = "opencode.client=web,service.instance.id=override,service.namespace=anomalyco"
+
+ expect(resource().attributes).toMatchObject({
+ "opencode.client": "cli",
+ "service.namespace": "anomalyco",
+ })
+ expect(resource().attributes["service.instance.id"]).not.toBe("override")
+ })
+})