DigiaLogger class

The SDK's one logging style, across core and every CEP plugin.

One instance per module, held as a const field so a call site is a method call and nothing more. The tag becomes the console prefix and is the whole retrieval story: a developer filters their console for DIGIA and pastes. Never reword the prefix; grepping for it is the only way a support ticket carries our logs.

<badge> [<TAG>] [<LEVEL>]: [<campaignKey>] <Message> (key=value, key=value)

🔴 [DIGIA] [ERROR]: [canvas_new_nudge] Payload parse failed
🟡 [DIGIA] [WARN]: [canvas_new_nudge] Dropped — Campaigns are still loading; trigger dropped
🔵 [DIGIA-ME] [INFO]: [canvas_new_nudge] Received (cepId=6aabbf01362b7cb5)
⚪ [DIGIA] [DEBUG]: [summer_sale] Dropped — frequency capped (policy=1/day)

The campaign slot is what makes same-campaign lines group vertically like a thread, and [<key>] an unambiguous filter — grepping the bare key also hits lines that merely mention it (by=summer_sale). It is a parameter, never hand-written brackets, so the format cannot rot; it is omitted entirely when there is no campaign (init, fetch, session, queue), never rendered empty; and it is always campaignKey, the human-meaningful name the author typed into the dashboard. cepCampaignId and presentationId stay in the trailing data.

There are exactly six tags, and the set is closed on purpose — nine were tried first and read as noise, because the SDK is one thing to the reader and sub-module detail belongs in the message when it matters:

Tag Written as Covers
DIGIA DigiaLogger() everything in core that is not one of the below
DIGIA-ANALYTICS DigiaLogger('analytics') the first-party analytics pipeline
DIGIA-LIVETEST DigiaLogger('liveTest') live campaign testing
DIGIA-CT / -WE / -ME in the plugin packages the CEP boundary — the #1 support question

Message style, so a reader can scan the left edge: outcome first, sentence case (Campaign received, not received campaign); never repeat the tag's module in the message; data trails in one parenthesised key=value group.

One emit, many sinks. Every call builds exactly one TimelineRecord and walks the sink registry; ConsoleSink renders the line above, and ScreenSink records the on-device campaign timeline. Adding a destination never touches a call site.

The two gates are independent, and that is the point:

Sink Accepts when Who owns the knob
ConsoleSink severity is at or above the configured DigiaLogLevel the host developer
ScreenSink the record carries a TimelineStage us, per call site
HealthSink the record's DiagnosticReason is on a central allowlist us, in one list

Each gate reads a different field with a different owner, which is what makes escalation fail closed: forgetting one can only narrow the audience, never widen it. There is deliberately no sendTo: parameter — per-call-site routing drifts, and one mistyped flag would leak internals to a campaign creator or to a backend.

So a debug gating drop that a release console suppresses still reaches the campaign creator's screen. Promoting a call is a deliberate act: pass a TimelineStage and a DiagnosticReason from a closed enum, and free-text developer logging can never leak onto a non-developer's screen. The promoted arguments do not change the console line — what a developer reads is message and nothing else.

Three rules the SDK's release chain makes non-negotiable, since a bad line ships inside a customer app for months:

  • A log call never throws. It runs on render paths. Every sink call is caught here, so a sink cannot break the app that hosts us.
  • A disabled level costs nothing. Gate an expensive unstaged message with isEnabled rather than building it and discarding it inside.
  • A staged call is never wrapped in isEnabled. That guard is for hot unstaged chatter only; around a staged call it silently blinds the timeline in exactly the release build someone opened it to debug.

Constructors

DigiaLogger([String tag = ''])
Creates a logger for one module.
const

Properties

hashCode → int
The hash code for this object.
no setterinherited
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited
tag → String
The module suffix in this logger's console prefix, or empty for none.
final

Methods

d(String message, {String? campaign, TimelineStage? stage, DiagnosticReason? reason, String? presentationId, Map<String, String>? extras}) → void
Per-node, per-frame, per-request detail.
e(String message, {String? campaign, Object? error, StackTrace? stackTrace, TimelineStage? stage, DiagnosticReason? reason, String? presentationId, Map<String, String>? extras}) → void
The SDK could not do the thing. Visible at every level but DigiaLogLevel.none.
i(String message, {String? campaign, TimelineStage? stage, DiagnosticReason? reason, String? presentationId, Map<String, String>? extras}) → void
A lifecycle milestone.
isEnabled(DigiaLogSeverity severity) → bool
Whether a severity line would be emitted right now.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toString() → String
A string representation of this object.
inherited
w(String message, {String? campaign, Object? error, TimelineStage? stage, DiagnosticReason? reason, String? presentationId, Map<String, String>? extras}) → void
Degraded, but recovered.

Operators

operator ==(Object other) → bool
The equality operator.
inherited

Static Properties

level → DigiaLogLevel
The threshold currently in force.
no setter
screenNameSource ↔ String? Function()?
Supplies the screen the app is currently on, so staged records can be stamped with it without every call site passing one.
getter/setter pair

Static Methods

configure(DigiaLogLevel level) → void
Applies the host app's configured verbosity. Called once from Digia.initialize(); until then DigiaLogLevel.auto is in force, so logs emitted during startup are not silently lost.
isSeverityEnabled(DigiaLogSeverity severity) → bool
Whether the configured level admits severity. The console's gate, and nothing else's.
registerSink(DiagnosticSink sink) → void
Adds a sink to the registry. Idempotent, so a repeated init cannot end up emitting a record twice into the same destination.
unregisterSink(DiagnosticSink sink) → void
Removes a sink. A no-op if it was never registered.
withCampaign<T>(String? campaign, T body()) → T
Runs body with campaign stamped on each record it logs that names none. A parse runs deep below the campaign that owns it.