Skip to content

Modernize to a first-class Exceptionless client (0.9, toward 1.0) #91

Open

Description

@ejsmith

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_MAPPER writes camelCase and nulls. The server now uses strict snake_case (System.Text.Json, #2135), so multi-word fields such as stack_trace, line_number, machine_name, email_address, signature_data are lost.
  • Field name/type mismatches beyond casing: filename → file_name, isSignatureTarget serializes as signatureTarget, secure → is_secure, query_string must be map<string,string>, headers must be map<string,string[]>.
  • PluginManager sorts plugins by descending priority, so ModuleInfoPlugin runs before ErrorPlugin and modules are never attached.
  • Cause chains are dropped (inner never set); submitException takes Exception, so Errors (OOM, StackOverflowError) can't be reported.
  • @ext holds the raw Throwable (~32 KB for a two-level exception); a serialization failure stalls the whole queue.

Submission / queue

  • SubmissionResponse.isRateLimited() is true whenever x-ratelimit-remaining is 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.match treats wildcard globs as regex: *password* throws PatternSyntaxException and the event is cancelled. Any project with a wildcard data exclusion or @@log:* setting loses events.
  • API key sent as ?access_token= instead of Authorization: Bearer.

Lifecycle / dependencies

  • 3–4 non-daemon Timer threads per client: the JVM can't exit. No close(), flush, or shutdown hook; queued events are lost at exit.
  • Configuration casts the root logger to logback (ClassCastException under 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 (authorization leaks on HTTP/2), data exclusions stop at depth 3, processName leaks 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.Logger for 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, string process_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:Type over 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) / submittedEvent hooks and exclusion callbacks; config from EXCEPTIONLESS_* env vars, system properties, exceptionless.properties; config lock after startup; proxy/TLS via HttpClient.
  • 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 @@log setting: Warn for logging adapters, no filter for direct submitLog.
  • 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)

  1. Treat java., javax., jdk., sun., kotlin., scala. (and common framework prefixes) as non-user namespaces in ErrorSignature.
  2. Normalize $ in declaring types like +.
  3. Add Java to docs; drop the "never advertise Java" line in AssistantSystemPrompt.cs once 2.0 ships.

Open items

  • Migrating the com.exceptionless namespace to a Sonatype Central Portal account (needed before any release).

Activity

  1. github-actions commented on Oct 19, 2026

    @github-actions

    Thanks for reporting this issue. We will get back to you in a while

  2. ejsmith commented on Oct 19, 2026

    @ejsmith
    MemberAuthor

    Server-side stacking follow-ups filed:

  3. changed the title [-]Modernize to a first-class Exceptionless client (2.0)[/-] [+]Modernize to a first-class Exceptionless client (0.9, toward 1.0)[/+] on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions