For the complete documentation index, see llms.txt. This page is also available as Markdown.

2.0

Release notes for Pharos Triton 2.0 — a breaking release with a static, builder-first API, distributed tracing with spans, and Apex performance marks.

Source commit: 83ba761

Status: Unreleased — 2.0 preview (targeting release)

Highlights

  • New static, builder-first API. Triton is now called statically — no more Triton.instance. Logs are composed with a builder and passed to level methods: Triton.info(...), Triton.error(...), Triton.logNow(...). A Triton.t shorthand builds from the active template. See The new logging API.

  • Distributed tracing with spans. New spanId() / parentSpanId() builder methods and Span_Id__c / Parent_Span_Id__c fields let you correlate work across transactions and services, alongside the existing traceable() / tracePoint() traceability controls.

  • Performance marks in Apex. New Triton.startMark() / Triton.endMark() time a stretch of Apex, emit a paired set of Performance records, and parent every log created inside the mark to it — with the mark name derived from the stack trace, so call sites carry no hand-typed strings. startTriggerMark() adds the trigger context a stack trace can't supply. See Performance marks in Apex.

  • Platform-cache transaction management. A new TritonCacheManager centralizes Org cache operations, and Triton.withCache() ties a transaction id across LWC / Apex / Flow hops using the platform cache.

  • Deferred flushing to conserve platform events. New Triton.drain() and Triton.flushTop(), plus a TritonFlowFlushBatch, let Flow and high-volume logging hand work off for batched publishing instead of consuming platform-event allocations inline.

  • Org-level runtime configuration. A new TritonSettings__c custom setting adds a master Disable Logging kill switch, a Disable Flow Flush Batch switch, and Cache TTL / Flow Flush Batch Delay knobs, while Log_Level__mdt gains Component and User matching with most-specific-rule-wins resolution — all editable from Pharos Admin. See Runtime configuration & admin controls.

  • Level and convenience builder methods. TritonBuilder gains .error(), .warning(), .info(), .debug(), .fine(), .finer(), .finest() (with optional summary overloads), plus .exception(e), .dmlResults(...), .apexExecutionContext(), and .operation(...).

  • Flow version capture. The Flow Log action gains a Flow Version input and TritonBuilder gains .flowVersion(Decimal), writing pharos__Flow_Version__c — so a Flow log carries the plain API name and its version separately, the way native Flow error logs do. See Flow version capture.

  • Log-level rule evaluation fixed. User-scoped rules now match for Apex logs, blank rule fields are true wildcards, and a malformed rule or an unparsable level can no longer break a transaction or drop a log. See Log-level rule fixes — one of these changes can also widen rules you already have, so read the Upgrade notes too.

  • Overhauled test infrastructure. A new TritonTestFactory, an expanded TritonTest, and a new TritonFlowTest bring comprehensive coverage of the static API, caching, and tracing.

  • Instrumentation skills + OpenTelemetry readiness. Eight agent-agnostic instrumentation skills take a codebase from audit → migrate → instrument → guard: read-only coverage reports, 1.x → 2.0 conversion, per-technology instrumentation for Apex, LWC, and Flows, org-wide orchestration in reviewed batches, CI guardrails, and a persona that runs the whole catalog — each with a human-in-the-loop diff review and an optional deploy. Combined with the new spans/tracing model, this makes your org OTEL-ready: Triton's trace/span/parent-span concepts map cleanly onto OpenTelemetry. See Instrumentation skills (companion tooling) and OpenTelemetry readiness.

The new logging API

The most significant change in 2.0 is the move from a singleton instance to a static, builder-first API.

Before (1.42 and earlier):

After (2.0):

Static log methods

Triton exposes buffered and immediate log methods, all of which accept a TritonBuilder:

Method
Behavior
Level

Triton.log(builder)

Buffer the log

uses builder's level

Triton.error(builder)

Buffer the log

overrides to ERROR

Triton.warning(builder)

Buffer the log

overrides to WARNING

Triton.info(builder)

Buffer the log

overrides to INFO

Triton.debug(builder)

Buffer the log

overrides to DEBUG

Triton.fine(builder) / finer(builder) / finest(builder)

Buffer the log

overrides to FINE / FINER / FINEST

Triton.logNow(builder)

Buffer and flush immediately

uses builder's level

Buffered logs are persisted when you call Triton.flush() (all logs), Triton.flushTop() (only the most recent), or Triton.drain() (hand the buffer off without publishing).

Templates and the Triton.t shorthand

Set a reusable template once, then start each log from it. Triton.t is shorthand for Triton.fromTemplate() and automatically carries the active transaction id:

Convenience builder methods

TritonBuilder now carries the level and common configuration itself:

  • Level methods — .error(), .warning(), .info(), .debug(), .fine(), .finer(), .finest(), each with an optional (String summary) overload.

  • .exception(Exception e) — one call sets the type (from the exception type, or Backend), ERROR level, summary, details, stack trace, and flags issue creation.

  • .dmlResults(List<Object>) — formats Database.SaveResult/DeleteResult/UpsertResult/UndeleteResult/MergeResult collections, capturing failed ids and error messages.

  • .apexExecutionContext() / .operation(String) — capture the current execution context and label the operation.

Category is now origin-only

TritonTypes.Category answers exactly one question — what technology produced this log — and the enum is now exactly:

Apex · Flow · LWC · Aura

Integration, Warning, Event, and Debug are gone. Warning, Event, and Debug described severity, which is what the log Level is for. Integration described a kind of work, which is what the Type and the attached payload are for.

Logging a callout. Keep the default Apex category and identify the log by its call type and payload:

An integration log is an ordinary Apex log with the HTTP exchange attached — see Integration Logs.

Deployed Flows and LWCs keep logging. A Flow Log action or an LWC that still passes a removed category string is not dropped. The entry is re-categorized to Flow or LWC, and a diagnostic note is appended to Details. The two tiers word it differently, so search for both:

Source
Note appended to Details

Flow

Unable to locate category: Integration. Default Flow category will be used.

LWC

Invalid Log Category: Integration. Default LWC category will be used.

Treat those notes as a to-do list: they name precisely which Flows and components still pass a category that no longer exists.

Apex gets no such grace. Triton ships as source, so Apex referencing a removed enum member fails to compile when you deploy 2.0.

Historical logs are untouched. Category is stored as a string on the log record, so logs written before the upgrade keep whatever they were written with.

Log_Level__mdt rules need re-keying. A rule keyed on Integration, Warning, Event, or Debug does not error — the Category field is free text, so the rule simply stops matching anything and that slice of logs falls back to whatever broader rule applies. Re-key those rules to a real origin category, or to a Level. See Upgrade notes.

Distributed tracing with spans

2.0 adds span-based tracing on top of Triton's existing transaction id and traceability:

  • spanId(String) — set the current span identifier (stored on Span_Id__c).

  • parentSpanId(String) — link to a parent span (stored on Parent_Span_Id__c).

  • traceable(Boolean) — mark a builder as traceable (also flags issue creation).

  • tracePoint() — emit a trace point when the builder is traceable.

The corresponding LWC layer (TritonLwc / triton.js) was updated to carry span and parent-span identifiers so front-end interactions can participate in the same trace. This aligns Triton's model with distributed-tracing conventions — see the Spans & Distributed Tracing guide for how these map to spans and traces.

Performance marks in Apex

Apex reaches parity with the LWC performance marks. A mark times a stretch of code, emits a paired set of records, and builds a span tree as it goes:

Method
Behavior

Triton.startMark()

Open a mark named <Class>.<method>, derived from the stack trace. Returns the name.

Triton.startMark(String)

Open a mark with an explicit name, for names that aren't method names.

Triton.endMark()

Close the innermost open mark.

Triton.endMark(String)

Close the newest mark with that name, for out-of-order completion.

Triton.clearMarks()

Discard all open marks and reset to the request root.

Triton.startTriggerMark()

Open a mark named <Handler>.<Trigger.operationType>. Zero arguments.

  • Paired records. A mark publishes Performance started: <mark> when it opens and Performance: <mark> with Duration: <n>ms when it closes, both on the same span — the same prefixes the LWC marks use, so both tiers render identically.

  • Ambient parenting. Marks nest LIFO, and an ordinary log emitted inside a mark becomes a child of it instead of a flat sibling. The outermost mark parents to the transaction span. Ids you set with .spanId(...) / .parentSpanId(...) are never overwritten.

  • Starting a transaction no longer destroys an open mark. Triton.startTransaction() resets the span hierarchy only when no mark is open. An LWC log save or a Flow Log action firing inside an open Apex mark used to discard that mark along with its unwritten completion record; now the mark survives and closes normally. Triton.clearMarks() remains the explicit way to reset.

  • Suppression is transparent. If a Log_Level__mdt rule filters out a mark's opening record, its children re-parent to the nearest surviving ancestor rather than being orphaned.

  • Buffered, always FINE. No mark method flushes, and there is no level argument — verbosity stays an admin's Log_Level__mdt decision rather than a constant baked into a call site.

  • Batch and async need no new API. Triton.resumeTransaction(carriedId) (if you carry one) followed by Triton.startMark() is the whole idiom; each chunk begins as its own subtree under the shared transaction.

Full guide: Performance Marks in Apex.

Flow version capture

Flow logs now record the flow's version in a field of its own instead of folding it into the name — the same split native Flow error logs use:

  • Flow → Log action: a new optional Flow Version input (flowVersion), alongside the existing Flow API Name. Flow passes it as a string.

  • Apex: TritonBuilder.flowVersion(Decimal), written to pharos__Flow_Version__c. The managed field is Text, so the number is stored in its canonical string form.

A value Triton can't parse never fails the log: it appends Unable to parse flow version: <value>. Flow Version will not be set. to Details and saves the log without the version.

OpenTelemetry readiness

The tracing model above isn't just useful inside Pharos — it's what makes a 2.0 org OpenTelemetry-ready. Triton's three correlation primitives line up directly with OTel's tracing vocabulary:

OpenTelemetry concept
Triton 2.0 equivalent

Trace

The transaction id (Triton.TRANSACTION_ID / this.triton.transactionId) correlating one end-to-end operation.

Span

Span_Id__c, set via .spanId(...).

Parent span

Parent_Span_Id__c, set via .parentSpanId(...).

Because the concepts map cleanly, instrumented Triton logs carry the structure an observability stack expects, and they're a solid foundation for a future OpenTelemetry integration.

Instrumentation skills (companion tooling)

2.0 is accompanied by a set of agent-agnostic instrumentation skills — portable playbooks surfaced as /pharos: slash commands in agents that support them (and runnable from any coding agent) — that automate adding Triton instrumentation to existing code, following the 2.0 static API and Triton best practices:

Instrument one technology:

  • /pharos:instrument-apex — instruments Apex classes and triggers: structure-aware resume-first transactions, logNow + rethrow in catches, .dmlResults(...) on partial DML, callout integrationPayload, System.debug → Triton.debug, and flush at the right boundaries.

  • /pharos:instrument-lwc — adds the c/triton logger, bindToComponent, and timeBackendCall (which carries span context), plus transaction stitching to Apex.

  • /pharos:instrument-flows — inserts TritonFlow "Log" actions with individual named inputs, instrumenting flow entry and every fault path.

Work across a codebase:

  • /pharos:audit-logging — a read-only scan that reports logging gaps and a prioritized instrumentation plan. Writes no source files; run it first for the map.

  • /pharos:migrate-to-2.0 — converts 1.x Triton.instance code to the 2.0 static API, stopping to ask on every call where a rename would silently change immediate-vs-buffered publishing.

  • /pharos:instrument-org — orchestrates the three per-technology skills across a whole project: consistent Areas per feature, transaction stitching across LWC → Apex → Flow, and a review checkpoint every batch.

  • /pharos:setup-standards — scaffolds the guardrails that keep an instrumented codebase instrumented: CI checks for System.debug and Triton.instance, a PR review checklist, and Log_Level__mdt defaults.

  • /pharos:instrumentation-agent — the persona that ties the catalog together and runs the full audit → migrate → instrument → guard sequence end to end, pausing for you between stages.

Each skill runs a human-in-the-loop loop — prerequisites check → analyze → apply → diff review (yes / modify / skip) → write → optional --deploy via the Salesforce CLI — so nothing is written until you approve it. Run them repeatedly (or loop an agent) across a backlog to roll out consistent instrumentation quickly.

Companion tooling — ships alongside 2.0, not inside the managed package. The skills live in the Triton repository's agent-skills/ directory on the 2.0 line. They are not part of the core Triton managed package and are never deployed into an org — they're developer tooling you run from your coding agent against your local project, and the code they generate targets Triton 2.0. See the Instrumentation Skills section for full usage.

Platform-cache transaction management

A new TritonCacheManager centralizes platform cache access (Org cache). Transaction handling is now cache-aware:

  • Triton.withCache() — enable platform cache for transaction management; resumes an existing cached transaction id if present, otherwise starts a new one. Falls back gracefully when cache is unavailable.

  • Triton.startTransaction() / resumeTransaction(id) / stopTransaction() — now cache the transaction id (and extend its TTL) when caching is enabled, making it easier to correlate logs across LWC / Apex / Flow transitions.

Deferred flushing & Flow batching

To reduce platform-event (PE) consumption under load, 2.0 introduces deferred flushing:

  • Triton.drain() — returns all buffered logs and clears the buffer without publishing, so a caller (such as Flow) can hand them off for batched processing.

  • Triton.flushTop() — publishes only the most recent buffered log immediately.

  • TritonFlowFlushBatch — a batch class that flushes Flow-originated logs, integrating with the cache manager so Flow logging consumes fewer PE allocations.

flush() and flushTop() continue to schedule delayed publishing with exponential backoff when PE allocation is near capacity (95%).

Runtime configuration & admin controls

2.0 moves logging behavior under org-level runtime configuration — a new TritonSettings__c custom setting plus richer Log_Level__mdt matching — all editable from the Triton area of Pharos Admin (Configuration → Settings / Log Levels) without a code change or redeploy.

Global switches (TritonSettings__c):

  • Disable Logging — a master kill switch. When set, all Triton logging (Apex, Flow, LWC) is short-circuited before logs are built or persisted.

  • Disable Flow Flush Batch — when set, Flow logs flush immediately (synchronously, in-transaction) instead of via the delayed TritonFlowFlushBatch. No logs are lost either way.

Operational knobs (TritonSettings__c):

  • Cache TTL (minutes) — lifetime of Triton's Org-cache entries (the transaction id and buffered Flow logs). Blank uses the packaged default (5 minutes).

  • Flow Flush Batch Delay (minutes) — how long the flush batch waits before it runs. Blank uses the packaged default (1 minute).

Log-level matching (Log_Level__mdt):

  • Rules now match on Component and User in addition to Category / Type / Area, so you can scope verbosity down to a single class, LWC, Flow, or user.

  • When several rules target a log, the most specific one wins and overrides the others in both directions — a narrow rule can raise or lower verbosity for its slice. Specificity is ranked User > Component > Area > Type > Category. See Log Levels.

Transaction-id cache moved to Org cache. Transaction correlation now uses Salesforce Org cache instead of Session cache, so a cached transaction id survives across asynchronous and session-less contexts (Batch, Queueable, Scheduled, @future). See Transactions & Platform Cache.

Log-level rule fixes

Four fixes to how Log_Level__mdt rules are evaluated. All four make rules behave the way the field labels already promised:

  • User-scoped rules now actually match. build() stamps pharos__User_Id__c with UserInfo.getUserId() when nothing else set it, so a User rule is evaluated against a real user id at filter time. Previously a User rule was inert for Apex logs: the id was filled in at flush time, after filtering had already run.

  • Blank rule fields are true wildcards. A field holding '' or whitespace now behaves exactly like an empty one. Previously only a genuine null was a wildcard, so a rule saved with a blank-looking field could silently match nothing.

  • Matching on Category, Type, Area, and Component is case-insensitive. These are free-text fields typed by hand, so apex and Apex now mean the same thing. User Id stays case-sensitive, compared on its 15-character form — Salesforce Ids are case-sensitive base-62, and folding their case would collide distinct users. This change can widen rules you already have; see Upgrade notes.

  • A bad rule can no longer break a transaction. A malformed Log_Level__mdt row — one with a blank Level, for instance — is skipped instead of throwing into the host transaction, and a log whose own level is unparsable is kept rather than dropped. Filtering fails open: when Triton can't decide, the log survives.

See Log Levels for how rules are scoped and resolved.

Governor-limit tracking

Limit tracking is unchanged from 1.42. limitInfo() captures the full set — SOQL queries and rows, DML statements and rows, CPU time, heap size, callouts, SOSL queries, aggregate queries, @future calls, queueable jobs, email invocations, push notifications, async (batch) calls, and immediate-publish DML — each alongside its governor ceiling. limitInfoLite() captures the essential subset, and withFullLimitInfo() / withoutLimitInfo() toggle between them.

Permission set updates

Triton_Read and Triton_Write were updated to include the new span fields — Span_Id__c and Parent_Span_Id__c. Both sets also grant Apex class access to LogLimitsController, which the Log Limits component on the Log record page needs.

After deploying 2.0, re-assign the permission sets (or refresh them through your deployment pipeline) so users pick up the new field-level access — and if you deploy the sets separately from the rest of 2.0, deploy them after the span fields, never before. Both steps are load-bearing: see Upgrade notes for what breaks if you skip either, and Configure Permissions for what each set grants.

Test suite

The Apex test infrastructure was substantially reworked:

  • TritonTestFactory — a new factory for constructing test logs and builders against the static API.

  • TritonTest — significantly expanded to cover static access, caching, spans, and deferred flushing.

  • TritonFlowTest — a new test class covering Flow logging and the flush batch.

Upgrade notes

  • Breaking: the singleton is gone. Replace all Triton.instance.<method>(...) calls with the static, builder-first equivalents. For example, Triton.instance.error(area, e) becomes Triton.error(Triton.t.area(area).exception(e)) (or set the area via a template).

  • Breaking: categories are origin-only. TritonTypes.Category is now exactly Apex · Flow · LWC · Aura; Integration, Warning, Event, and Debug are removed, and Apex referencing them won't compile. Also re-key any Log_Level__mdt rule that named a removed category — and note that a stale rule gives you no warning: the Category field is plain text, not a picklist, so the rule doesn't error, it just silently stops matching and that slice of logs reverts to whatever broader rule applies. Deployed Flows and LWCs keep logging with a diagnostic note in Details; historical logs keep their stored category strings. Full detail: Category is now origin-only.

  • Fix up flowApiName on already-instrumented Flows. Component-scoped log levels match the Flow API Name verbatim, so a version-suffixed value such as My_Flow-4 will not match a rule naming My_Flow. Move the version into the new Flow Version input and leave the plain API name behind. See Flow version capture.

  • Case-insensitive rule matching can widen rules you already have. Category / Type / Area / Component now match case-insensitively, so a rule field typed apex — which previously matched nothing, because the log's category is Apex — now matches every Apex log. Review your existing Log_Level__mdt rows for values whose case doesn't match what they were meant to target: a rule that had quietly never fired may start firing after the upgrade.

  • Buffered vs immediate naming changed. The old add* (buffer) / bare (instant) method pairs are replaced by Triton.log(...) / level methods (buffer) and Triton.logNow(...) (immediate). Audit any code that relied on the instantaneous default methods to ensure logs are still flushed when expected.

  • Upgrade Triton before you deploy the Triton permission sets. Triton_Read and Triton_Write grant Span_Id__c and Parent_Span_Id__c — fields Triton adds to pharos__Log__c itself — so deploying the sets into an org still on an older Triton fails with an invalid-field error. This error depends on your Triton version, not your Pharos package version. Upgrade Triton first, then deploy the permission sets, then re-assign them; a single deploy of the whole 2.0 source works too, because the fields and the sets land together. If you've already hit the error, finish the Triton upgrade and deploy again — nothing needs undoing.

  • Re-assign the permission sets — and know what a missing Triton_Write costs. Re-assignment isn't cosmetic. Triton publishes a platform event, and a separate subscriber process performs the pharos__Log__c insert; if that subscriber's running user lacks Triton_Write, logs still land — silently missing their limit and span data, with no error raised anywhere. So empty limit fields on new logs are a permission-set symptom first: check the subscriber's permission sets before you go looking at governor limits. See Configure Permissions.

  • Platform cache is opt-in. Call Triton.withCache() to enable cache-backed transaction management; without it, behavior falls back to the standard transaction id handling.

  • Tracing is additive. spanId(), parentSpanId(), traceable(), and tracePoint() are optional; existing logs continue to work without them.

  • Marks are opt-in, but they reshape span fields once used — including Span_Id__c on ordinary logs inside a mark. See Performance marks in Apex before adopting them.

Last updated