Repository navigation
Modernize to a first-class Exceptionless client (0.9, toward 1.0) #91
Copy link
Copy link
Open
Description
Summary
A review of this client against the latest Exceptionless server (main @ c165cdd, 2026-10-19) and the current JS/.NET clients found that the client's wire format no longer matches what the server reads: error stacks, environment info, request info, manual stacking, and user descriptions are silently dropped. Only 0.1.0 has ever shipped (2021), so the plan is a clean 2.0 rewrite that makes this a first-class Exceptionless client with a great developer experience.
Findings in the current client
Wire contract (data the server drops)
Utils.JSON_MAPPERwrites camelCase and nulls. The server now uses strict snake_case (System.Text.Json, #2135), so multi-word fields such asstack_trace,line_number,machine_name,email_address,signature_dataare lost.- Field name/type mismatches beyond casing:
filename→file_name,isSignatureTargetserializes assignatureTarget,secure→is_secure,query_stringmust bemap<string,string>,headersmust bemap<string,string[]>. PluginManagersorts plugins by descending priority, soModuleInfoPluginruns beforeErrorPluginand modules are never attached.- Cause chains are dropped (
innernever set);submitExceptiontakesException, soErrors (OOM, StackOverflowError) can't be reported. @extholds the rawThrowable(~32 KB for a two-level exception); a serialization failure stalls the whole queue.
Submission / queue
SubmissionResponse.isRateLimited()is true wheneverx-ratelimit-remainingis present, which the server sends on every response. 400/413/5xx all suspend and keep the batch; one bad batch blocks the queue forever.Utils.matchtreats wildcard globs as regex:*password*throwsPatternSyntaxExceptionand the event is cancelled. Any project with a wildcard data exclusion or@@log:*setting loses events.- API key sent as
?access_token=instead ofAuthorization: Bearer.
Lifecycle / dependencies
- 3–4 non-daemon
Timerthreads per client: the JVM can't exit. Noclose(), flush, or shutdown hook; queued events are lost at exit. Configurationcasts the root logger to logback (ClassCastExceptionunder Log4j2/JUL) and adds a root appender per construction. logback-classic 1.2 + logback-core 1.5 are mismatched compile deps.- Lombok 1.18.14 fails on JDK 21; Jackson/OkHttp versions are stale; OkHttp drags in Kotlin and conflicts with Spring Boot.
Other bugs
- Privacy: case-sensitive header filtering (
authorizationleaks on HTTP/2), data exclusions stop at depth 3,processNameleaks hostname,isIncludeMachineName()returns the IP flag. - In-memory storage, queue state, and duplicate checker are not thread-safe; duplicate checker hash includes
now()so it never matches. - Settings polled every 12s (other clients: 2 min).
DX
- No automatic capture: no uncaught-exception handler, no logging appenders, no Servlet/Spring integration.
- Request info expects
java.net.http.HttpRequest(an HTTP client type). - Publishing targets OSSRH (sunset June 2025); javadoc jar never built. Samples reference a non-existent version; README sample doesn't compile.
Server behaviours a Java client must honour
| Behavior | Client requirement |
|---|---|
Content-Encoding: deflate is raw deflate (DeflateStream) |
Use gzip (or new Deflater(level, true)) |
One wrong-typed top-level field turns the whole batch into log events |
Golden-JSON contract tests |
User-Agent / X-Exceptionless-Client must parse as name/x.y.z |
Send exceptionless-java/2.0.0 (no -SNAPSHOT) |
No Retry-After; 429 windows are 15-min UTC buckets per org |
Suspend until next 15-min boundary |
| 200,000 byte request limit (413) | Cap batches by bytes, shrink on 413 |
| Reference ids seen in last 24h are dropped | Unique reference ids per occurrence |
| Events older than 3 days are discarded | Persisted queue max age < 3 days |
@@log:* filtering is client-side only |
Implement wildcard + level semantics from .NET |
Only System/Microsoft treated as non-user namespaces; $ not normalized |
Server PR (below) |
Architecture for 2.0
- Java 17 baseline, package
com.exceptionless, multi-module Maven build + BOM. - Zero-dependency core: JDK
HttpClient, small internal JSON writer/reader (avoids Jackson 2 vs 3 conflicts),System.Loggerfor internal diagnostics, no Lombok,module-info, no reflection (GraalVM friendly). - Modules:
exceptionless-core,exceptionless-logback,exceptionless-log4j2,exceptionless-jul,exceptionless-servlet(Jakarta),exceptionless-spring-boot-starter,exceptionless-bom. - API mirrors JS/.NET:
Exceptionless.startup(c -> c.apiKey("...").version("1.2.3").useSessions()); Exceptionless.submitException(e); Exceptionless.createException(e).addTags("checkout").setProperty("order", order) .setUserIdentity(user.id(), user.name()).submit(); try (var client = ExceptionlessClient.create(cfg)) { ... } // AutoCloseable, flushes
Phases
- Phase 0 – Foundation: multi-module build, Maven wrapper,
maven.compiler.release, formatting, CI on JDK 17/21/25 with current actions, Central Portal publishing (central-publishing-maven-plugin), signed artifacts, javadoc jar, tag-based versions. - Phase 1 – Wire contract: immutable models with exact snake_case names/types (incl.
o_s_name/o_s_version, stringprocess_id/thread_id);Throwable→ error model with cause + suppressed chain, package/class/method/file/line frames; bounded@ext; modules from jar manifests /pom.properties; golden JSON contract tests + HTTP fake server tests. - Phase 2 – Reliability: single daemon scheduler, non-blocking submit; .NET-equivalent status handling (429/503 suspend+keep, 402 discard+clear, 401/403 15m+drop, 400/404 4h+drop, 413 shrink); byte-capped gzip batches; opt-in persisted file queue with attempts + max age; shutdown hook with bounded flush; chained uncaught-exception handler; settings every 2 min + config-version header refresh, persisted.
- Phase 3 – Feature parity: full event builder;
@@log:level/source wildcards,@@error:Typeover cause chain, type/source exclusions, bot UA patterns, recursive@@DataExclusions,@@IncludePrivateInformation; privacy toggles default on; sessions + heartbeats; reference ids; duplicate checker; version auto-detect;submittingEvent(cancellable) /submittedEventhooks and exclusion callbacks; config fromEXCEPTIONLESS_*env vars, system properties,exceptionless.properties; config lock after startup; proxy/TLS viaHttpClient. - Phase 4 – Integrations: Logback, Log4j2, JUL adapters (honouring server log levels); Jakarta Servlet filter; Spring Boot starter (properties, auto-config, MVC/WebFlux exception hooks, lifecycle flush); samples that build in CI.
- Phase 5 – DX & launch: README quick start, javadoc, migration notes from 0.1.0, agent skill (
.agents/skills/exceptionless-java), 2.0.0 release to Central.
Defaults chosen
- Minimum log level with no
@@logsetting: Warn for logging adapters, no filter for directsubmitLog. - Reference ids on by default for errors (server dedups reused ids for 24h).
- Duplicate checker: errors only, 30s window.
Companion server changes (separate PRs on exceptionless/Exceptionless)
- Treat
java.,javax.,jdk.,sun.,kotlin.,scala.(and common framework prefixes) as non-user namespaces inErrorSignature. - Normalize
$in declaring types like+. - Add Java to docs; drop the "never advertise Java" line in
AssistantSystemPrompt.csonce 2.0 ships.
Open items
- Migrating the
com.exceptionlessnamespace to a Sonatype Central Portal account (needed before any release).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
github-actions commentedon Oct 19, 2026
Thanks for reporting this issue. We will get back to you in a while
ejsmith commentedon Oct 19, 2026
Server-side stacking follow-ups filed:
java.*,kotlin.*, ...) as non-user code@extlookup soerrorCode/SQLStatesplit stacks[-]Modernize to a first-class Exceptionless client (2.0)[/-][+]Modernize to a first-class Exceptionless client (0.9, toward 1.0)[/+]