Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Preserve supplied environment casing
  • Loading branch information
ejsmith committed Sep 10, 2026
commit 0e675b4caa5dcd9909156cd22c0219e864680df3
2 changes: 1 addition & 1 deletion .agents/skills/exceptionless-javascript/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Use this skill to produce source-accurate setup code, integration guidance, and

Keep answers compact. Prefer pointing to official docs for broad product behavior, and use local package READMEs/source to correct stale snippets or repo-specific package details.

Deployment environments use `config.environment` or `config.setEnvironment(name)` as the default, and `builder.setEnvironment(name)` for overrides. They serialize as top-level `environment`, separately from `data.@environment`. See [configuration.md](references/configuration.md).
Deployment environments use `config.environment` or `config.setEnvironment(name)` as the default, and `builder.setEnvironment(name)` for overrides. They serialize as top-level `environment`, separately from `data.@environment`, preserving the supplied casing after trimming. See [configuration.md](references/configuration.md).

## Official Docs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ await Exceptionless.startup((config) => {

## Deployment environment

Set `config.environment = "production"` or call `config.setEnvironment("production")`. Per-event `setEnvironment("staging")` overrides the default. Names are trimmed and lowercased; empty names, names longer than 64 characters, and control characters are ignored. Missing values remain unspecified. This property is independent of `data.@environment` runtime metadata and of the application version. It does not change server stack grouping or create per-environment status.
Set `config.environment = "production"` or call `config.setEnvironment("production")`. Per-event `setEnvironment("staging")` overrides the default. Names are trimmed and retain their supplied casing; empty names, names longer than 64 characters, and control characters are ignored. Missing values remain unspecified. The server filters case-insensitively and normalizes aggregation keys. This property is independent of `data.@environment` runtime metadata and of the application version. It does not change server stack grouping or create per-environment status.

## Privacy

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The definition of the word exceptionless is: to be without exception. Exceptionl

## Browser

Set a deployment environment in startup configuration with `config.environment = "production"` (or `config.setEnvironment("production")`). Override it on an event with `Exceptionless.createLog("Example").setEnvironment("staging").submit()`. Names are trimmed, lowercased, and limited to 64 characters. Missing or invalid names remain unspecified. The top-level `environment` is separate from machine/runtime diagnostics in `data.@environment`; stacks and fixed versions remain shared across environments.
Set a deployment environment in startup configuration with `config.environment = "production"` (or `config.setEnvironment("production")`). Override it on an event with `Exceptionless.createLog("Example").setEnvironment("staging").submit()`. Names are trimmed and limited to 64 characters, preserving the supplied casing. The server filters case-insensitively and normalizes aggregation keys. Missing or invalid names remain unspecified. The top-level `environment` is separate from machine/runtime diagnostics in `data.@environment`; stacks and fixed versions remain shared across environments.

You can install the npm package via `npm install @exceptionless/browser --save`
or via cdn [`https://unpkg.com/@exceptionless/browser`](https://unpkg.com/@exceptionless/browser).
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ export function normalizeEnvironment(value: string | null | undefined): string |
if (typeof value !== "string") {
return undefined;
}
const name = value.trim().toLowerCase();
const name = value.trim();
// eslint-disable-next-line no-control-regex -- Deployment names cannot contain control characters.
return name && name.length <= 64 && !/[\u0000-\u001f\u007f-\u009f]/u.test(name) ? name : undefined;
}
8 changes: 5 additions & 3 deletions packages/core/test/configuration/Configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { describe, expect, test } from "vitest";
import { Configuration } from "../../src/configuration/Configuration.js";

describe("Configuration", () => {
test("should normalize deployment environments and allow clearing the default", () => {
test("should trim deployment environments, preserve casing, and allow clearing the default", () => {
const config = new Configuration();
expect(config.environment).toBeUndefined();
config.setEnvironment(" Production ");
expect(config.environment).toBe("production");
expect(config.environment).toBe("Production");
config.environment = "Staging";
expect(config.environment).toBe("staging");
expect(config.environment).toBe("Staging");
config.environment = "İ".repeat(64);
expect(config.environment).toBe("İ".repeat(64));
config.setEnvironment("");
expect(config.environment).toBeUndefined();
config.setEnvironment("x".repeat(65));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ describe("ConfigurationDefaultsPlugin", () => {
for (const type of ["error", "log", "usage", "session"]) {
const event: Event = { type, data: { "@environment": { machine_name: "worker-1" } } };
await plugin.run(new EventPluginContext(client, event, new EventContext()));
expect(event.environment).toBe("production");
expect(event.environment).toBe("Production");
expect(event.data?.["@environment"]?.machine_name).toBe("worker-1");
}

const builder = client.createLog("test", "message").setEnvironment(" Staging ");
await plugin.run(new EventPluginContext(client, builder.target, new EventContext()));
expect(builder.target.environment).toBe("staging");
expect(JSON.parse(JSON.stringify(builder.target)).environment).toBe("staging");
expect(builder.target.environment).toBe("Staging");
expect(JSON.parse(JSON.stringify(builder.target)).environment).toBe("Staging");
});

test.each(["", " ", "x".repeat(65), "prod\ninvalid", "İ".repeat(64)])("should keep invalid override %j unspecified", async (environment) => {
test.each(["", " ", "x".repeat(65), "prod\ninvalid"])("should keep invalid override %j unspecified", async (environment) => {
const client = new ExceptionlessClient();
client.config.environment = "production";
const builder = client.createLog("test", "message").setEnvironment(environment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,16 @@ describe("DuplicateCheckerPlugin", () => {

test("should merge duplicates only within the same environment", async () => {
const enqueue = vi.spyOn(client.config.services.queue, "enqueue");
expect((await run(Exception1StackTrace, "Production")).cancelled).not.toBe(true);
expect((await run(Exception1StackTrace, "production")).cancelled).not.toBe(true);
expect((await run(Exception1StackTrace, "staging")).cancelled).not.toBe(true);
expect((await run(Exception1StackTrace)).cancelled).not.toBe(true);
expect((await run(Exception1StackTrace, "Production")).cancelled).toBe(true);
expect((await run(Exception1StackTrace, "production")).cancelled).toBe(true);
expect((await run(Exception1StackTrace, "staging")).cancelled).toBe(true);
expect((await run(Exception1StackTrace)).cancelled).toBe(true);
await plugin.suspend();
expect(enqueue.mock.calls.map(([event]) => event.environment).sort()).toEqual(["production", "staging", undefined]);
expect(enqueue.mock.calls.map(([event]) => event.environment).sort()).toEqual(["Production", "production", "staging", undefined]);
});

test("should ignore error without stack", async () => {
Expand Down