From 5f2ffb8ff69a791ccc1dfdc2be21bd3b89990dc7 Mon Sep 17 00:00:00 2001 From: Sebastian Lorenz Date: Wed, 29 Jul 2026 16:42:48 +0000 Subject: [PATCH 1/2] Prefer explicit OTLP service identity --- .changeset/explicit-otel-service-identity.md | 5 +++ .../unstable/observability/OtlpResource.ts | 26 +++++++---- .../observability/OtlpResource.test.ts | 44 ++++++++++++++----- 3 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 .changeset/explicit-otel-service-identity.md diff --git a/.changeset/explicit-otel-service-identity.md b/.changeset/explicit-otel-service-identity.md new file mode 100644 index 00000000000..3613cf5074d --- /dev/null +++ b/.changeset/explicit-otel-service-identity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prefer explicit OTLP resource service names and versions over environment configuration. diff --git a/packages/effect/src/unstable/observability/OtlpResource.ts b/packages/effect/src/unstable/observability/OtlpResource.ts index 8602e53ce9a..d22ae5503c4 100644 --- a/packages/effect/src/unstable/observability/OtlpResource.ts +++ b/packages/effect/src/unstable/observability/OtlpResource.ts @@ -70,10 +70,20 @@ export const make = (options: { * Creates an OTLP resource from explicit options and OpenTelemetry * configuration. * + * **When to use** + * + * Use when resource metadata may be configured in code or by the deployment + * environment. To let operators set the service identity, omit `serviceName`, + * `serviceVersion`, and their matching attributes, then use + * `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`. + * * **Details** * - * `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`, and - * `OTEL_SERVICE_VERSION` override explicit options; missing required + * Explicit `serviceName` and `serviceVersion` options take precedence over + * matching explicit attributes and environment variables. Explicit attributes + * take precedence over `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and + * `OTEL_RESOURCE_ATTRIBUTES` for service identity. Other attributes from + * `OTEL_RESOURCE_ATTRIBUTES` override explicit attributes. Missing required * configuration is converted to a defect. * * @category constructors @@ -95,16 +105,16 @@ export const fromConfig: ( "OTEL_RESOURCE_ATTRIBUTES" ) - const serviceName = (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_NAME")) - ?? env?.["service.name"] as string | undefined + const serviceName = options?.serviceName ?? options?.attributes?.["service.name"] as string | undefined - ?? options?.serviceName + ?? (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_NAME")) + ?? env?.["service.name"] as string | undefined ?? (yield* Config.string("OTEL_SERVICE_NAME")) - const serviceVersion = (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_VERSION")) - ?? env?.["service.version"] as string | undefined + const serviceVersion = options?.serviceVersion ?? options?.attributes?.["service.version"] as string | undefined - ?? options?.serviceVersion + ?? (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_VERSION")) + ?? env?.["service.version"] as string | undefined const attributes = { ...options?.attributes, diff --git a/packages/effect/test/unstable/observability/OtlpResource.test.ts b/packages/effect/test/unstable/observability/OtlpResource.test.ts index 5ca89675693..9babab1bc97 100644 --- a/packages/effect/test/unstable/observability/OtlpResource.test.ts +++ b/packages/effect/test/unstable/observability/OtlpResource.test.ts @@ -7,20 +7,22 @@ const attributesRecord = (resource: OtlpResource.Resource): Record { describe("fromConfig", () => { - it.effect("uses OTEL service variables before explicit options", () => + it.effect("uses explicit service options before attributes and environment variables", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ serviceName: "explicit-service", serviceVersion: "explicit-version", attributes: { - "custom.attribute": "explicit" + "custom.attribute": "explicit", + "service.name": "explicit-attribute-service", + "service.version": "explicit-attribute-version" } }) assert.deepStrictEqual(attributesRecord(resource), { "custom.attribute": "explicit", - "service.name": "env-service", - "service.version": "env-version" + "service.name": "explicit-service", + "service.version": "explicit-version" }) }).pipe( Effect.provideService( @@ -28,17 +30,16 @@ describe("OtlpResource", () => { ConfigProvider.fromEnv({ env: { OTEL_SERVICE_NAME: "env-service", - OTEL_SERVICE_VERSION: "env-version" + OTEL_SERVICE_VERSION: "env-version", + OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version" } }) ) )) - it.effect("uses OTEL resource attributes before explicit options", () => + it.effect("uses explicit service attributes before environment variables", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ - serviceName: "explicit-service", - serviceVersion: "explicit-version", attributes: { "custom.attribute": "explicit", "service.name": "explicit-attribute-service", @@ -48,14 +49,16 @@ describe("OtlpResource", () => { assert.deepStrictEqual(attributesRecord(resource), { "custom.attribute": "env", - "service.name": "env-attribute-service", - "service.version": "env-attribute-version" + "service.name": "explicit-attribute-service", + "service.version": "explicit-attribute-version" }) }).pipe( Effect.provideService( ConfigProvider.ConfigProvider, ConfigProvider.fromEnv({ env: { + OTEL_SERVICE_NAME: "env-service", + OTEL_SERVICE_VERSION: "env-version", OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version,custom.attribute=env" } @@ -63,6 +66,27 @@ describe("OtlpResource", () => { ) )) + it.effect("uses dedicated service variables before OTEL resource attributes", () => + Effect.gen(function*() { + const resource = yield* OtlpResource.fromConfig() + + assert.deepStrictEqual(attributesRecord(resource), { + "service.name": "env-service", + "service.version": "env-version" + }) + }).pipe( + Effect.provideService( + ConfigProvider.ConfigProvider, + ConfigProvider.fromEnv({ + env: { + OTEL_SERVICE_NAME: "env-service", + OTEL_SERVICE_VERSION: "env-version", + OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version" + } + }) + ) + )) + it.effect("omits service.version when it is not configured", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ From 12a80f668c3d4ea1ce1f3cdd323c9439cf337927 Mon Sep 17 00:00:00 2001 From: Sebastian Lorenz Date: Wed, 29 Jul 2026 16:57:46 +0000 Subject: [PATCH 2/2] Prefer explicit OTLP resource attributes --- .changeset/explicit-otel-service-identity.md | 2 +- .../src/unstable/observability/OtlpResource.ts | 13 ++++++------- .../unstable/observability/OtlpResource.test.ts | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.changeset/explicit-otel-service-identity.md b/.changeset/explicit-otel-service-identity.md index 3613cf5074d..a34030e3364 100644 --- a/.changeset/explicit-otel-service-identity.md +++ b/.changeset/explicit-otel-service-identity.md @@ -2,4 +2,4 @@ "effect": patch --- -Prefer explicit OTLP resource service names and versions over environment configuration. +Prefer explicit OTLP resource configuration over environment configuration. diff --git a/packages/effect/src/unstable/observability/OtlpResource.ts b/packages/effect/src/unstable/observability/OtlpResource.ts index d22ae5503c4..20ecffcfa27 100644 --- a/packages/effect/src/unstable/observability/OtlpResource.ts +++ b/packages/effect/src/unstable/observability/OtlpResource.ts @@ -80,11 +80,10 @@ export const make = (options: { * **Details** * * Explicit `serviceName` and `serviceVersion` options take precedence over - * matching explicit attributes and environment variables. Explicit attributes - * take precedence over `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and - * `OTEL_RESOURCE_ATTRIBUTES` for service identity. Other attributes from - * `OTEL_RESOURCE_ATTRIBUTES` override explicit attributes. Missing required - * configuration is converted to a defect. + * matching explicit attributes. Explicit attributes take precedence over + * environment variables. `OTEL_SERVICE_NAME` and `OTEL_SERVICE_VERSION` take + * precedence over matching attributes in `OTEL_RESOURCE_ATTRIBUTES`. Missing + * required configuration is converted to a defect. * * @category constructors * @since 4.0.0 @@ -117,8 +116,8 @@ export const fromConfig: ( ?? env?.["service.version"] as string | undefined const attributes = { - ...options?.attributes, - ...env + ...env, + ...options?.attributes } delete attributes["service.name"] diff --git a/packages/effect/test/unstable/observability/OtlpResource.test.ts b/packages/effect/test/unstable/observability/OtlpResource.test.ts index 9babab1bc97..c4de1a60f01 100644 --- a/packages/effect/test/unstable/observability/OtlpResource.test.ts +++ b/packages/effect/test/unstable/observability/OtlpResource.test.ts @@ -37,7 +37,7 @@ describe("OtlpResource", () => { ) )) - it.effect("uses explicit service attributes before environment variables", () => + it.effect("uses explicit attributes before environment variables", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ attributes: { @@ -48,7 +48,7 @@ describe("OtlpResource", () => { }) assert.deepStrictEqual(attributesRecord(resource), { - "custom.attribute": "env", + "custom.attribute": "explicit", "service.name": "explicit-attribute-service", "service.version": "explicit-attribute-version" })