Skip to content

Latest commit

 

History

History
376 lines (277 loc) · 16.7 KB

File metadata and controls

376 lines (277 loc) · 16.7 KB

Configuration Reference


Builder API (Observarium.builder())

All configuration for a plain Java setup goes through the fluent builder returned by Observarium.builder(). Each method returns the builder for chaining. Call .build() to obtain the immutable Observarium instance.

Method Type Default Description
scrubLevel(ScrubLevel) ScrubLevel BASIC Controls which PII patterns are active. See Scrub Levels below.
addScrubPattern(Pattern) java.util.regex.Pattern Adds a single compiled regex to the active set. Each match is replaced with [REDACTED]. Can be called multiple times.
fingerprinter(ExceptionFingerprinter) ExceptionFingerprinter DefaultExceptionFingerprinter Replaces the built-in fingerprinter. See Custom Fingerprinter.
scrubber(DataScrubber) DataScrubber DefaultDataScrubber Replaces the built-in scrubber entirely. When set, scrubLevel and addScrubPattern are ignored. See Custom Scrubber.
traceContextProvider(TraceContextProvider) TraceContextProvider MdcTraceContextProvider Replaces the MDC-based trace reader. See Custom TraceContextProvider.
addPostingService(PostingService) PostingService Appends a posting service to the list. Can be called multiple times.
postingServices(List<PostingService>) List<PostingService> Replaces the entire posting service list at once.
listener(ObservariumListener) ObservariumListener no-op Registers a lifecycle listener that receives callbacks for exception captures, drops, and posting outcomes. Used by observarium-micrometer to bridge events to Micrometer meters. See ObservariumListener.
queueCapacity(int) int 256 Capacity of the bounded ArrayBlockingQueue that backs the single background worker thread. When the queue is full, new events are dropped and a warning is logged.
maxDuplicateComments(int) int 5 Maximum number of duplicate comments posted on a single existing issue before further recurrences are dropped silently. Use -1 for unlimited. See Duplicate Comment Limit.

Minimum working example:

Observarium obs = Observarium.builder()
    .addPostingService(new GitHubPostingService(
        GitHubConfig.of("ghp_token", "owner", "repo")))
    .build();

Spring Boot Properties

All properties are under the observarium prefix. Use either application.yml or application.properties.

Property Type Default Description
observarium.scrub-level NONE | BASIC | STRICT BASIC PII scrub level applied to messages and stack traces.
observarium.github.enabled boolean false Enable the GitHub posting service.
observarium.github.token String GitHub personal access token or fine-grained token.
observarium.github.owner String GitHub repository owner (organization name or user login).
observarium.github.repo String GitHub repository name.
observarium.github.label-prefix String observarium Label applied to all issues created by Observarium.
observarium.jira.enabled boolean false Enable the Jira posting service.
observarium.jira.base-url String Jira instance URL, e.g. https://myorg.atlassian.net.
observarium.jira.username String Jira account username (email address for Jira Cloud).
observarium.jira.api-token String Jira API token.
observarium.jira.project-key String Jira project key, e.g. OPS.
observarium.jira.issue-type String Bug Jira issue type name for created issues.
observarium.gitlab.enabled boolean false Enable the GitLab posting service.
observarium.gitlab.base-url String GitLab instance URL, e.g. https://gitlab.com.
observarium.gitlab.private-token String GitLab personal access token or project access token.
observarium.gitlab.project-id String GitLab numeric project ID or namespace/project path.
observarium.email.enabled boolean false Enable the Email posting service.
observarium.email.smtp-host String SMTP server hostname.
observarium.email.smtp-port int 587 SMTP server port.
observarium.email.from String Sender address.
observarium.email.to String Recipient address.
observarium.email.username String SMTP authentication username.
observarium.email.password String SMTP authentication password.
observarium.email.auth boolean true Enable SMTP authentication.
observarium.email.start-tls boolean true Enable STARTTLS.
observarium.max-duplicate-comments int 5 Maximum number of duplicate comments posted on a single existing issue. Use -1 for unlimited. See Duplicate Comment Limit.

Example application.yml:

observarium:
  scrub-level: STRICT
  github:
    owner: acme
    repo: backend
    token: ${GITHUB_TOKEN}
  jira:
    base-url: https://acme.atlassian.net
    username: ${JIRA_USERNAME}
    api-token: ${JIRA_TOKEN}
    project-key: OPS

Quarkus Properties

Identical keys to Spring Boot; use application.properties or application.yaml.

The Quarkus module uses the same property names as the Spring Boot module, including observarium.max-duplicate-comments. Refer to the Spring Boot table above for the complete list.

Example application.properties:

observarium.scrub-level=STRICT
observarium.github.owner=acme
observarium.github.repo=backend
observarium.github.token=${GITHUB_TOKEN}

Scrub Levels

The ScrubLevel enum controls which regular expressions DefaultDataScrubber applies to exception messages and full stack trace text. Every match is replaced with the literal string [REDACTED].

NONE

No patterns are applied. The raw exception message and stack trace are sent to the posting service unchanged.

Use this only in development environments where the data contains no production PII.

BASIC (default)

Applies patterns that target credentials and tokens likely to appear in exception messages:

Pattern Example match
Key-value credentials password=hunter2, token: abc123, api_key=xyz
Bearer tokens Bearer eyJhbGciO...
// Input:  "Connection failed: password=supersecret host=db.internal"
// Output: "Connection failed: [REDACTED] host=db.internal"

STRICT

Applies all BASIC patterns plus patterns for personal data:

Pattern Example match
Email addresses user@example.com
IPv4 addresses 192.168.1.42
Phone numbers (US format) 555-867-5309, 5558675309
// Input:  "User alice@example.com from 10.0.0.5 called support at 555-123-4567"
// Output: "User [REDACTED] from [REDACTED] called support at [REDACTED]"

Custom Scrub Patterns

Additional patterns are applied after all built-in patterns at the active level. The replacement is always [REDACTED].

import java.util.regex.Pattern;

Observarium obs = Observarium.builder()
    // Redact internal order IDs: ORD-followed by digits
    .addScrubPattern(Pattern.compile("ORD-\\d+"))
    // Redact UUIDs
    .addScrubPattern(Pattern.compile(
        "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
        Pattern.CASE_INSENSITIVE))
    .addPostingService(...)
    .build();

Custom patterns are additive: they do not replace the built-in level's patterns.


Custom Scrubber

To bypass DefaultDataScrubber entirely, implement DataScrubber and pass it to the builder with .scrubber(). When a custom scrubber is provided, scrubLevel and addScrubPattern are ignored.

import io.hephaistos.observarium.scrub.DataScrubber;

public class MyDataScrubber implements DataScrubber {

    @Override
    public String scrub(String text) {
        if (text == null) {
            return null;
        }
        // Replace all digits with *
        return text.replaceAll("\\d", "*");
    }
}

Observarium obs = Observarium.builder()
    .scrubber(new MyDataScrubber())
    .addPostingService(...)
    .build();

Custom Fingerprinter

DefaultExceptionFingerprinter computes a SHA-256 hash over the exception class name, every frame's className#methodName, and the class names of the full cause chain. Line numbers are excluded so the fingerprint is stable across minor refactors.

To override, implement ExceptionFingerprinter:

import io.hephaistos.observarium.fingerprint.ExceptionFingerprinter;

public class TopFrameFingerprinter implements ExceptionFingerprinter {

    @Override
    public String fingerprint(Throwable throwable) {
        // Group by exception type and top frame only
        StackTraceElement top = throwable.getStackTrace().length > 0
            ? throwable.getStackTrace()[0]
            : null;
        String key = throwable.getClass().getName()
            + (top != null ? "#" + top.getClassName() + "." + top.getMethodName() : "");
        return Integer.toHexString(key.hashCode());
    }
}

Observarium obs = Observarium.builder()
    .fingerprinter(new TopFrameFingerprinter())
    .addPostingService(...)
    .build();

Custom TraceContextProvider

MdcTraceContextProvider reads trace_id and span_id from SLF4J MDC. The default key names match what the OpenTelemetry Java Agent and most tracing bridges write to MDC.

Override the keys when your tracing library uses different names:

import io.hephaistos.observarium.trace.MdcTraceContextProvider;

// Brave / Spring Cloud Sleuth uses "traceId" and "spanId"
Observarium obs = Observarium.builder()
    .traceContextProvider(new MdcTraceContextProvider("traceId", "spanId"))
    .addPostingService(...)
    .build();

Implement TraceContextProvider from scratch when MDC is not the right source:

import io.hephaistos.observarium.trace.TraceContextProvider;
import io.opentelemetry.api.trace.Span;

public class OtelApiTraceContextProvider implements TraceContextProvider {

    @Override
    public String getTraceId() {
        Span span = Span.current();
        return span.getSpanContext().isValid()
            ? span.getSpanContext().getTraceId()
            : null;
    }

    @Override
    public String getSpanId() {
        Span span = Span.current();
        return span.getSpanContext().isValid()
            ? span.getSpanContext().getSpanId()
            : null;
    }
}

Observarium obs = Observarium.builder()
    .traceContextProvider(new OtelApiTraceContextProvider())
    .addPostingService(...)
    .build();

Async Behaviour

Observarium.captureException() returns immediately with a CompletableFuture<List<PostingResult>>. The actual work (fingerprinting, scrubbing, HTTP calls to the issue tracker) executes on a single daemon background thread backed by an ArrayBlockingQueue.

Key properties:

  • Single worker thread — events are processed in submission order, no concurrency within Observarium itself.
  • Bounded queue — when the queue reaches queueCapacity (default 256), new events are dropped silently except for a WARN log line: "Observarium queue full, dropping exception report". This protects the application from backpressure caused by a slow issue tracker.
  • Shutdown — both the JVM shutdown hook and obs.shutdown() wait up to 10 seconds for in-flight work to complete, then force shutdown only if the drain times out, and then close all posting services. obs.shutdown() blocks for the duration of this sequence. Call it explicitly when you need to stop processing before JVM exit, for example in a @PreDestroy method.
  • Failure isolation — if a posting service throws an unchecked exception, ExceptionProcessor catches it, logs it at ERROR, and returns a PostingResult.failure(...). The application thread that called captureException is never affected.
// Inspect results if you need to know the outcome
CompletableFuture<List<PostingResult>> future =
    obs.captureException(e, Severity.ERROR);

future.thenAccept(results ->
    results.forEach(r -> {
        if (r.success()) {
            System.out.println("Issue: " + r.url());
        } else {
            System.err.println("Failed: " + r.errorMessage());
        }
    })
);

ObservariumListener

ObservariumListener is a callback interface in observarium-core that lets you observe the internal lifecycle of the processing pipeline without modifying core logic. All methods have no-op defaults; implement only the events you care about.

Method Called when Thread
onExceptionCaptured(Severity) An exception is successfully enqueued Caller's thread
onExceptionDropped() An exception is dropped because the queue is full Caller's thread
onPostingCompleted(serviceName, duplicate, success, durationNanos) A posting service finishes processing Background worker thread
onQueueSizeAvailable(Supplier<Integer>) The Observarium instance is constructed; provides a live queue-depth supplier Construction thread

Implementations must be thread-safe and must not throw. Any exception thrown from a callback is caught and logged but otherwise ignored.

Register a listener via the builder:

Observarium obs = Observarium.builder()
    .listener(new MyObservariumListener())
    .addPostingService(...)
    .build();

The primary built-in use of this interface is ObservariumMeterBinder from observarium-micrometer, which bridges these callbacks to Micrometer meters. See Micrometer Integration for setup details.


Duplicate Comment Limit

When an exception recurs frequently, Observarium caps the number of duplicate comments posted on an existing issue to prevent issue tracker noise.

Behaviour by threshold

For each duplicate occurrence, ExceptionProcessor retrieves the current comment count from DuplicateSearchResult and compares it against maxDuplicateComments:

Condition Action
commentCount < maxDuplicateComments Normal commentOnIssue call — the recurrence is appended to the issue.
commentCount == maxDuplicateComments postCommentLimitNotice is called once — a final "Comment Limit Reached" notice is posted on the issue.
commentCount > maxDuplicateComments The occurrence is dropped silently. observarium.comments.dropped counter is incremented. ObservariumListener.onCommentDropped(serviceName) is called.

Note: The notice itself is an additional comment, so the total number of comments Observarium may post is maxDuplicateComments + 1 (N regular comments plus the final notice). For example, with maxDuplicateComments=5, up to 6 comments may appear on the issue: 5 duplicate occurrence comments and 1 limit notice.

Comment count source

The comment count is read from the tracker API during findDuplicate():

Backend API field
GitHub comments field on the issue JSON
GitLab user_notes_count field on the issue JSON
Jira fields.comment.total from the issue response

The count reflects all comments on the issue, not just those posted by Observarium. This means comments left by human users, bots, or other integrations also count toward the limit. This is intentional: if an issue already has significant discussion, additional automated noise is unwanted regardless of who posted the existing comments.

GitLab caveat: GitLab's user_notes_count includes system-generated notes (label changes, milestone updates, etc.) in addition to user comments. This means the limit may trigger earlier than expected on issues with frequent label or milestone activity.

Custom posting services and fail-open behaviour

DuplicateSearchResult.found(id, url) (the 2-argument form) returns COMMENT_COUNT_UNKNOWN = -1 for the comment count. When ExceptionProcessor sees -1, it treats the count as below the limit and always allows the comment through. This means custom PostingService implementations that have not been updated to return a comment count continue to work without restriction. See Custom Posting Service for how to supply the count.

Configuration example

Plain Java builder:

Observarium obs = Observarium.builder()
    .maxDuplicateComments(10)          // cap at 10 comments per issue
    // .maxDuplicateComments(-1)       // unlimited
    .addPostingService(new GitHubPostingService(GitHubConfig.of(token, "owner", "repo")))
    .build();

Spring Boot / Quarkus property:

observarium.max-duplicate-comments=10