Skip to content

Commit f7b9ca2

Browse files
ejsmithclaude
andcommitted
Normalize environments assigned by later plugins
A plugin that runs after the defaults plugin could set an untrimmed, oversized, or control-character environment that reached the queue. Normalize again before submitting and before resubmitting merged duplicates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 55b2fa9 commit f7b9ca2

6 files changed

Lines changed: 50 additions & 9 deletions

File tree

‎packages/core/src/ExceptionlessClient.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { EventContext } from "./models/EventContext.js";
77
import { EventPluginContext } from "./plugins/EventPluginContext.js";
88
import { EventPluginManager } from "./plugins/EventPluginManager.js";
99
import { PluginContext } from "./plugins/PluginContext.js";
10-
import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "./Utils.js";
10+
import { allowProcessToExitWithoutWaitingForTimerOrInterval, setNormalizedEnvironment } from "./Utils.js";
1111

1212
export class ExceptionlessClient {
1313
private _intervalId: ReturnType<typeof setInterval> | undefined;
@@ -248,6 +248,9 @@ export class ExceptionlessClient {
248248
ev.date = new Date();
249249
}
250250

251+
// Plugins that run after the defaults can assign any value.
252+
setNormalizedEnvironment(ev, ev.environment);
253+
251254
await this.config.services.queue.enqueue(ev);
252255

253256
if (ev.reference_id && ev.reference_id.length > 0) {

‎packages/core/src/Utils.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Event } from "./models/Event.js";
2+
13
export function getHashCode(source: string): number {
24
if (!source || source.length === 0) {
35
return 0;
@@ -567,3 +569,15 @@ export function normalizeEnvironment(value: string | null | undefined): string |
567569
// eslint-disable-next-line no-control-regex -- Deployment names cannot contain control characters.
568570
return name && name.length <= 64 && !/[\u0000-\u001f\u007f-\u009f]/u.test(name) ? name : undefined;
569571
}
572+
573+
/**
574+
* Sets the event's environment to the normalized value, or removes it when the value is not a valid name.
575+
*/
576+
export function setNormalizedEnvironment(event: Event, value: string | null | undefined): void {
577+
const environment = normalizeEnvironment(value);
578+
if (environment) {
579+
event.environment = environment;
580+
} else {
581+
delete event.environment;
582+
}
583+
}

‎packages/core/src/plugins/default/ConfigurationDefaultsPlugin.ts‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty, normalizeEnvironment, stringify } from "../../Utils.js";
1+
import { isEmpty, setNormalizedEnvironment, stringify } from "../../Utils.js";
22
import { EventPluginContext } from "../../plugins/EventPluginContext.js";
33
import { IEventPlugin } from "../../plugins/IEventPlugin.js";
44

@@ -9,12 +9,7 @@ export class ConfigurationDefaultsPlugin implements IEventPlugin {
99
public run(context: EventPluginContext): Promise<void> {
1010
const { dataExclusions, defaultData, defaultTags } = context.client.config;
1111
const ev = context.event;
12-
const environment = normalizeEnvironment(ev.environment ?? context.client.config.environment);
13-
if (environment) {
14-
ev.environment = environment;
15-
} else {
16-
delete ev.environment;
17-
}
12+
setNormalizedEnvironment(ev, ev.environment ?? context.client.config.environment);
1813

1914
if (defaultTags) {
2015
ev.tags = [...(ev.tags || []), ...defaultTags];

‎packages/core/src/plugins/default/DuplicateCheckerPlugin.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { InnerErrorInfo } from "../../models/data/ErrorInfo.js";
22
import { KnownEventDataKeys } from "../../models/Event.js";
3-
import { allowProcessToExitWithoutWaitingForTimerOrInterval, getHashCode } from "../../Utils.js";
3+
import { allowProcessToExitWithoutWaitingForTimerOrInterval, getHashCode, setNormalizedEnvironment } from "../../Utils.js";
44
import { EventPluginContext } from "../../plugins/EventPluginContext.js";
55
import { IEventPlugin } from "../../plugins/IEventPlugin.js";
66

@@ -123,6 +123,8 @@ class MergedEvent {
123123

124124
public async resubmit(): Promise<void> {
125125
this._context.event.count = this._count;
126+
// Plugins between the defaults and this one can assign any value.
127+
setNormalizedEnvironment(this._context.event, this._context.event.environment);
126128
await this._context.client.config.services.queue.enqueue(this._context.event);
127129
}
128130

‎packages/core/test/ExceptionlessClient.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ describe("ExceptionlessClient", () => {
3737
expect(context.event.reference_id).toBe(lastReferenceIdManager.getLast());
3838
});
3939

40+
test.each([
41+
[" QA\t", "QA"],
42+
["x".repeat(65), undefined]
43+
])("should normalize environment %j assigned by a later plugin", async (environment, expected) => {
44+
const client = new ExceptionlessClient();
45+
client.config.apiKey = "UNIT_TEST_API_KEY";
46+
client.config.environment = "production";
47+
client.config.addPlugin("late-environment", 90, (context) => {
48+
context.event.environment = environment;
49+
return Promise.resolve();
50+
});
51+
const enqueue = vi.spyOn(client.config.services.queue, "enqueue").mockResolvedValue();
52+
53+
await client.submitLog("test");
54+
55+
expect(enqueue).toHaveBeenCalledOnce();
56+
expect(enqueue.mock.calls[0]?.[0].environment).toBe(expected);
57+
});
58+
4059
test("should accept undefined source", () => {
4160
const client = new ExceptionlessClient();
4261
client.config.apiKey = "UNIT_TEST_API_KEY";

‎packages/core/test/plugins/default/DuplicateCheckerPlugin.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ describe("DuplicateCheckerPlugin", () => {
100100
expect(enqueue.mock.calls.map(([event]) => event.environment).sort()).toEqual(["Production", "production", "staging", undefined]);
101101
});
102102

103+
test("should normalize the environment of a resubmitted duplicate", async () => {
104+
const enqueue = vi.spyOn(client.config.services.queue, "enqueue").mockResolvedValue();
105+
await run(Exception1StackTrace, " staging\n");
106+
expect((await run(Exception1StackTrace, " staging\n")).cancelled).toBe(true);
107+
await plugin.suspend();
108+
expect(enqueue.mock.calls.map(([event]) => event.environment)).toEqual(["staging"]);
109+
});
110+
103111
test("should ignore error without stack", async () => {
104112
await run();
105113
const contextOfSecondRun = await run();

0 commit comments

Comments
 (0)