diff --git a/README.md b/README.md index a1aaad4..fbbfc29 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,11 @@ resolved fresh on every call, so changes take effect immediately: 1. `LOG_LEVEL` env var (`debug` | `info` | `warn` | `error` | `silent`) — wins if set. 2. `setLogLevel(level)` or `setupOtel({ logLevel })`. -3. Default: `debug` in development (`DEPLOYMENT_ENV` unset or `development`), `info` otherwise. +3. Default: `debug` only when `DEPLOYMENT_ENV=development`; `info` otherwise (including when `DEPLOYMENT_ENV` is unset). + +**Upgrading from v3.2.0:** an unset `DEPLOYMENT_ENV` previously selected `debug`. +New releases select `info`; set `LOG_LEVEL=debug` to preserve the old verbosity without +changing the deployment identity. ```ts import { setLogLevel } from "@photon-ai/otel"; @@ -161,7 +165,7 @@ Standard OpenTelemetry env vars always take precedence over `SetupOtelOptions`: | `OTEL_EXPORTER_OTLP__HEADERS` | Trace-, log-, or metric-specific headers; override generic and code headers. | | `OTEL_METRIC_EXPORT_INTERVAL` | Metric export interval in milliseconds. Defaults to `60000`. | | `OTEL_METRIC_EXPORT_TIMEOUT` | Metric export timeout in milliseconds. Defaults to `30000`. | -| `DEPLOYMENT_ENV` | Attached as `deployment.environment` resource attribute. Defaults to `development`. Also drives the default log level. | +| `DEPLOYMENT_ENV` | Attached as `deployment.environment`; only that resource attribute falls back to `development` when unset. Logging falls back to `info` unless the value is explicitly `development`. | | `LOG_LEVEL` | Minimum log level: `debug` \| `info` \| `warn` \| `error` \| `silent`. Overrides `setLogLevel()` / `setupOtel({ logLevel })`. | ## Automatic fetch instrumentation diff --git a/docs/configuration.mdx b/docs/configuration.mdx index 083bb6c..288722f 100644 --- a/docs/configuration.mdx +++ b/docs/configuration.mdx @@ -140,12 +140,13 @@ Avoid putting per-request values in resource attributes. Use span attributes or `DEPLOYMENT_ENV` is attached as the resource attribute `deployment.environment`. -If unset, it defaults to `development`. +If unset, only that resource attribute defaults to `development`. Log-level +resolution still sees the variable as unset and defaults to `info`. -It also affects the default log level: +The raw environment value selects the default log level: -- `debug` when `DEPLOYMENT_ENV` is unset or `development` -- `info` in every other environment +- `debug` only when `DEPLOYMENT_ENV` is `development` +- `info` otherwise, including when `DEPLOYMENT_ENV` is unset ## Log level @@ -249,7 +250,7 @@ The package always excludes its own OTLP exporter endpoints from fetch instrumen | Metric endpoint | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | base endpoint + `/v1/metrics` | | Signal headers | signal-specific `*_HEADERS` | generic headers, then `setupOtel({ headers })` | | Log level | `LOG_LEVEL` | `setupOtel({ logLevel })` or `setLogLevel()` | -| Deployment environment | `DEPLOYMENT_ENV` | `development` | +| `deployment.environment` resource attribute | `DEPLOYMENT_ENV` | `development` | ## Best practices diff --git a/docs/guides/logging.mdx b/docs/guides/logging.mdx index 70210a2..5de3dcc 100644 --- a/docs/guides/logging.mdx +++ b/docs/guides/logging.mdx @@ -110,8 +110,12 @@ console.log(getLogLevel()); // "warn", unless LOG_LEVEL is set Defaults: -- `debug` when `DEPLOYMENT_ENV` is unset or `development` -- `info` otherwise +- `debug` only when `DEPLOYMENT_ENV` is `development` +- `info` otherwise, including when `DEPLOYMENT_ENV` is unset + +> **Upgrading from v3.2.0:** an unset `DEPLOYMENT_ENV` previously selected +> `debug`. Set `LOG_LEVEL=debug` to preserve the old verbosity without changing +> the deployment identity. Allowed values: diff --git a/docs/reference/api.mdx b/docs/reference/api.mdx index 0b14506..4591d4d 100644 --- a/docs/reference/api.mdx +++ b/docs/reference/api.mdx @@ -151,7 +151,9 @@ Returns the current effective log level. function getLogLevel(): LogLevel; ``` -The value is resolved from `LOG_LEVEL`, then programmatic override, then deployment default. +The value is resolved from `LOG_LEVEL`, then programmatic override, then the raw +`DEPLOYMENT_ENV` value (`debug` only for explicit `development`; `info` +otherwise, including when unset). ## `LogLevel` diff --git a/docs/reference/environment.mdx b/docs/reference/environment.mdx index 07e240c..cc2e09c 100644 --- a/docs/reference/environment.mdx +++ b/docs/reference/environment.mdx @@ -19,7 +19,7 @@ Environment variables are deployment-owned configuration. They override code def | `OTEL_EXPORTER_OTLP__HEADERS` | Signal-specific exporter headers | generic and code headers | | `OTEL_METRIC_EXPORT_INTERVAL` | Metric export interval in milliseconds | default `60000` | | `OTEL_METRIC_EXPORT_TIMEOUT` | Metric export timeout in milliseconds | default `30000` | -| `DEPLOYMENT_ENV` | deployment resource attribute and default log-level input | default `development` | +| `DEPLOYMENT_ENV` | deployment resource attribute and default log-level selector | resource attribute uses `development` when unset; logger uses `info` | | `LOG_LEVEL` | logger minimum severity | `setupOtel({ logLevel })` and `setLogLevel()` | ## `OTEL_EXPORTER_OTLP_ENDPOINT` @@ -116,12 +116,14 @@ DEPLOYMENT_ENV=production The value is attached to telemetry as `deployment.environment`. -If unset, the package uses `development`. +If unset, only the `deployment.environment` resource attribute uses +`development`. The logger reads the raw variable and therefore still sees it as +unset. -It also controls the default log level: +The raw value separately controls the default log level: -- `development` or unset: `debug` -- any other value: `info` +- `development`: `debug` +- any other value, including unset: `info` ## `LOG_LEVEL` diff --git a/src/logger.ts b/src/logger.ts index ead8be2..a6d6b19 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -30,9 +30,9 @@ function envLevel(): LogLevel | undefined { } function defaultLevel(): LogLevel { - return (process.env.DEPLOYMENT_ENV ?? "development") === "development" - ? "debug" - : "info"; + // Read the raw env value: the resource attribute's separate `development` + // fallback must not opt an embedding SDK into debug logging. + return process.env.DEPLOYMENT_ENV === "development" ? "debug" : "info"; } /** @@ -41,7 +41,8 @@ function defaultLevel(): LogLevel { * match the rest of the package's config story): * 1. `LOG_LEVEL` env var * 2. `setLogLevel()` / `setupOtel({ logLevel })` - * 3. environment-driven default (`debug` in development, `info` otherwise) + * 3. environment-driven default (`debug` only when `DEPLOYMENT_ENV=development`; + * `info` otherwise, including when `DEPLOYMENT_ENV` is unset) */ function resolveLevel(): LogLevel { return envLevel() ?? levelOverride ?? defaultLevel(); diff --git a/src/setup.ts b/src/setup.ts index e2d150f..0ede831 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -75,8 +75,9 @@ export interface SetupOtelOptions { instrumentFetch?: boolean | InstrumentFetchOptions; /** * Minimum log level emitted by `createLogger()` (to both OTLP and console). - * The `LOG_LEVEL` env var still takes precedence. Defaults to `debug` in - * development and `info` otherwise. + * The `LOG_LEVEL` env var still takes precedence. Defaults to `debug` only + * when `DEPLOYMENT_ENV=development`; `info` otherwise, including when the + * variable is unset. */ logLevel?: LogLevel; /** diff --git a/tests/logger.test.ts b/tests/logger.test.ts index c663ce1..335aaef 100644 --- a/tests/logger.test.ts +++ b/tests/logger.test.ts @@ -190,23 +190,31 @@ describe("getLogLevel resolution", () => { vi.resetModules(); delete process.env.LOG_LEVEL; process.env.DEPLOYMENT_ENV = "development"; - const fresh = await import("../src/logger"); - expect(fresh.getLogLevel()).toBe("debug"); + const loggerModule = await import("../src/logger"); + expect(loggerModule.getLogLevel()).toBe("debug"); + }); + + it("env-driven default is info when DEPLOYMENT_ENV is unset", async () => { + vi.resetModules(); + delete process.env.LOG_LEVEL; + delete process.env.DEPLOYMENT_ENV; + const loggerModule = await import("../src/logger"); + expect(loggerModule.getLogLevel()).toBe("info"); }); it("env-driven default is info outside development", async () => { vi.resetModules(); delete process.env.LOG_LEVEL; process.env.DEPLOYMENT_ENV = "production"; - const fresh = await import("../src/logger"); - expect(fresh.getLogLevel()).toBe("info"); + const loggerModule = await import("../src/logger"); + expect(loggerModule.getLogLevel()).toBe("info"); }); it("ignores an invalid LOG_LEVEL value", async () => { vi.resetModules(); process.env.LOG_LEVEL = "loud"; process.env.DEPLOYMENT_ENV = "production"; - const fresh = await import("../src/logger"); - expect(fresh.getLogLevel()).toBe("info"); + const loggerModule = await import("../src/logger"); + expect(loggerModule.getLogLevel()).toBe("info"); }); }); diff --git a/tests/setup.test.ts b/tests/setup.test.ts index fcb4bd3..dc4a605 100644 --- a/tests/setup.test.ts +++ b/tests/setup.test.ts @@ -1,7 +1,7 @@ import dc from "node:diagnostics_channel"; import { metrics } from "@opentelemetry/api"; import { MeterProvider as SdkMeterProvider } from "@opentelemetry/sdk-metrics"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { isOtelActive, setupOtel } from "../src/setup"; const ENV_KEYS = [ @@ -16,6 +16,7 @@ const ENV_KEYS = [ "OTEL_METRIC_EXPORT_INTERVAL", "OTEL_METRIC_EXPORT_TIMEOUT", "DEPLOYMENT_ENV", + "LOG_LEVEL", ] as const; // Fetch instrumentation has two strategies and only one touches @@ -69,6 +70,24 @@ describe("setupOtel", () => { expect(second).toBe(first); }); + it("lets logLevel override the deployment default but not LOG_LEVEL", async () => { + vi.resetModules(); + process.env.DEPLOYMENT_ENV = "development"; + const otelModule = await import("../src/index"); + const handle = otelModule.setupOtel({ + serviceName: "log-level-precedence", + logLevel: "warn", + }); + try { + expect(otelModule.getLogLevel()).toBe("warn"); + + process.env.LOG_LEVEL = "error"; + expect(otelModule.getLogLevel()).toBe("error"); + } finally { + await handle.shutdown(); + } + }); + it("works with no endpoint configured (graceful no-op exporters)", () => { const handle = setupOtel({ serviceName: "no-endpoint" }); expect(handle).toBeDefined();