Skip to content

Add deployment environments to events - #98

Open
ejsmith wants to merge 2 commits into
mainfrom
feature/event-environments
Open

ejsmith wants to merge 2 commits into
mainfrom
feature/event-environments

Conversation

@ejsmith

@ejsmith ejsmith commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Events can now carry a deployment environment such as production or staging. It is sent as the optional root environment property of the event, next to type, source and message, and it is separate from data.@environment, which still holds machine information and is untouched. This matches the .NET client (Exceptionless.Net #371) and the JavaScript client (Exceptionless.JavaScript #193), and the server support in Exceptionless #2570.

Self-hosted installs should upgrade their server first. Older servers drop the root environment property, so events sent by this client to an older server simply arrive without an environment.

What changed

  • ExceptionlessConfiguration.environment(String) sets the default environment, with getEnvironment() and isEnvironmentConfigured() to read it.
  • EventBuilder.setEnvironment(String) overrides the default for one event. A non-null name that is not valid (empty or whitespace, over 64 characters, or containing control characters) is still an override, so that event is sent unspecified and does not fall back to the default. setEnvironment(null) means no override: it clears any earlier call on that builder and the configured default applies, as in the .NET and JavaScript clients.
  • ConfigurationDefaultsPlugin applies the default to every event that does not already have one, including errors, logs, feature usage, 404s and session events. Events set by hand with Event.setEnvironment keep their value.
  • Event has a new environment property, and toJson writes it at the event root only when it is set.
  • Names are normalized the way the server does it: trimmed (including non-breaking and other Unicode spaces, which String.strip() would keep), casing preserved, and treated as unspecified when empty, longer than 64 characters or containing control characters. Event.setEnvironment and the configuration setters enforce this, so every path to the queue, including plugins, submitting handlers and the duplicate checker's merged events, produces a valid payload.
  • The duplicate checker includes the environment when it compares errors, so the same error in two environments is queued separately (the server still groups them into one stack), as in the other clients.
  • The Spring Boot starter reads exceptionless.environment and, as a last resort, uses the active Spring profile when exactly one profile is active.
  • The README has a "Deployment environments" section and the Java agent skill documents the rules.

How precedence works

From highest to lowest:

  1. A per-event EventBuilder.setEnvironment(...) with a non-null value. A valid name is used; an invalid one leaves that event unspecified. Null is not an override and falls through to the lower levels.
  2. An environment already on the event.
  3. The default set in code with ExceptionlessConfiguration.environment(...).
  4. External configuration, which applies only when code has not set one: exceptionless.properties, then EXCEPTIONLESS_ENVIRONMENT, then the exceptionless.environment system property (the existing order for external sources), and the Spring exceptionless.environment property in the starter. This holds whichever is applied first, so reading external configuration after setting a value in code does not replace it.
  5. In the Spring Boot starter only, the active profile through ExceptionlessConfiguration.environmentFallback(...), used only when no environment is configured at all and exactly one profile is active. Several profiles such as prod,metrics do not name an environment, so none is guessed.

As in .NET, a configured value that is empty or invalid counts as configured: it leaves events unspecified and blocks lower-priority sources, rather than silently falling through to them. Passing null to environment(...) forgets the value set in code.

How I tested it

I ran ./mvnw verify for the whole reactor in Docker (maven:3.9-eclipse-temurin) on JDK 17, 21 and 25, matching the CI matrix. After the last change (null means no override) I re-ran it on JDK 17 and 21; CI covers the rest of the matrix. That includes Spotless, Error Prone and NullAway on JDK 21 and up, and all 432 core tests plus the logging, servlet and Spring Boot starter modules (also with -Pspring-boot-4 before that change). All pass. The end-to-end tests against a real server (-De2e=true) were not run locally.

New tests cover:

  • normalization edge cases: trimming, empty, 64 versus 65 characters, control characters, Unicode spaces;
  • precedence of code against external configuration in both read orders, invalid values, clearing, and the fallback;
  • builder override, null meaning no override (including after an earlier valid or invalid override), invalid names leaving the event unspecified, and default application for every event type;
  • plugins and submitting handlers that set an invalid value;
  • serialization with the root property present and absent, and data.@environment left alone;
  • the duplicate checker keeping environments apart;
  • the Spring property, profile fallback and customizer precedence.

I also mutation-checked the key behavior by breaking each of these in turn and confirming tests fail: the default application, the per-event override flag (null counted as an override, and invalid names not counted as one), setter normalization, serialization, code-over-external precedence, the 64 character limit, the control character check, the Unicode trim, the duplicate hash, the fallback and both Spring paths. Every mutant was caught.

🤖 Generated with Claude Code

Events get an optional root `environment` property, separate from the
`@environment` machine data. The client configuration holds a default
(`environment(String)`, `exceptionless.environment`,
`EXCEPTIONLESS_ENVIRONMENT`), `EventBuilder.setEnvironment` overrides it
per event, and the Spring Boot starter falls back to the only active
profile. Names are normalized like the server does: trimmed, and
unspecified when empty, over 64 characters or containing control
characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T13:18:49.396350Z deba42a New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

EventBuilder.setEnvironment(null) now clears any earlier override on the
builder so the configured default applies, matching the .NET and
JavaScript clients. Only a non-null value that is not valid (empty or
white space, over 64 characters, control characters) is an explicit
override that sends the event without an environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant