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.
Tritonis now called statically — no moreTriton.instance. Logs are composed with a builder and passed to level methods:Triton.info(...),Triton.error(...),Triton.logNow(...). ATriton.tshorthand builds from the active template. See The new logging API.Distributed tracing with spans. New
spanId()/parentSpanId()builder methods andSpan_Id__c/Parent_Span_Id__cfields let you correlate work across transactions and services, alongside the existingtraceable()/tracePoint()traceability controls.Performance marks in Apex. New
Triton.startMark()/Triton.endMark()time a stretch of Apex, emit a paired set ofPerformancerecords, 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
TritonCacheManagercentralizes Org cache operations, andTriton.withCache()ties a transaction id across LWC / Apex / Flow hops using the platform cache.Deferred flushing to conserve platform events. New
Triton.drain()andTriton.flushTop(), plus aTritonFlowFlushBatch, 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__ccustom setting adds a master Disable Logging kill switch, a Disable Flow Flush Batch switch, and Cache TTL / Flow Flush Batch Delay knobs, whileLog_Level__mdtgains 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.
TritonBuildergains.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
TritonBuildergains.flowVersion(Decimal), writingpharos__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 expandedTritonTest, and a newTritonFlowTestbring 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:
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, orBackend), ERROR level, summary, details, stack trace, and flags issue creation..dmlResults(List<Object>)— formatsDatabase.SaveResult/DeleteResult/UpsertResult/UndeleteResult/MergeResultcollections, 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:
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 onSpan_Id__c).parentSpanId(String)— link to a parent span (stored onParent_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:
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 andPerformance: <mark>withDuration: <n>mswhen 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__mdtrule 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 nolevelargument — verbosity stays an admin'sLog_Level__mdtdecision rather than a constant baked into a call site.Batch and async need no new API.
Triton.resumeTransaction(carriedId)(if you carry one) followed byTriton.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 topharos__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:
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, calloutintegrationPayload,System.debug→Triton.debug, and flush at the right boundaries./pharos:instrument-lwc— adds thec/tritonlogger,bindToComponent, andtimeBackendCall(which carries span context), plus transaction stitching to Apex./pharos:instrument-flows— insertsTritonFlow"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.xTriton.instancecode 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 forSystem.debugandTriton.instance, a PR review checklist, andLog_Level__mdtdefaults./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.
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()stampspharos__User_Id__cwithUserInfo.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 genuinenullwas 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
apexandApexnow 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__mdtrow — 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)becomesTriton.error(Triton.t.area(area).exception(e))(or set the area via a template).Breaking: categories are origin-only.
TritonTypes.Categoryis now exactlyApex·Flow·LWC·Aura;Integration,Warning,Event, andDebugare removed, and Apex referencing them won't compile. Also re-key anyLog_Level__mdtrule 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
flowApiNameon already-instrumented Flows. Component-scoped log levels match the Flow API Name verbatim, so a version-suffixed value such asMy_Flow-4will not match a rule namingMy_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 isApex— now matches every Apex log. Review your existingLog_Level__mdtrows 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 byTriton.log(...)/ level methods (buffer) andTriton.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_ReadandTriton_WritegrantSpan_Id__candParent_Span_Id__c— fields Triton adds topharos__Log__citself — 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_Writecosts. Re-assignment isn't cosmetic. Triton publishes a platform event, and a separate subscriber process performs thepharos__Log__cinsert; if that subscriber's running user lacksTriton_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(), andtracePoint()are optional; existing logs continue to work without them.Marks are opt-in, but they reshape span fields once used — including
Span_Id__con ordinary logs inside a mark. See Performance marks in Apex before adopting them.
Last updated