> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/common-apex-usage-patters/full-control-with-tritonbuilder.md).

# Full Control with TritonBuilder

The complete TritonBuilder surface — every method worth knowing, the deep-dive helpers, custom attributes, and the template pattern in full.

`TritonBuilder` is the fluent object that describes a single log. You've seen it throughout this section as `Triton.t` and `Triton.makeBuilder()`; this page is the full tour — everything the builder can do, and how to keep your logging code DRY with templates.

If you just want the mental model, [The Builder & Templates](/core-concepts/builder-and-templates.md) covers it. This page is the reference you come back to when you need a specific capability.

## Two ways to start a builder

{% code lineNumbers="true" %}

```apex
Triton.makeBuilder()   // a fresh builder, pre-set to the Apex category
Triton.t               // a clone of your template, stamped with the current transaction id
```

{% endcode %}

Reach for `Triton.t` almost always — it carries your [template](#templates) fields for free. Use `Triton.makeBuilder()` when you want a clean slate or you're building a template itself.

## Turning a builder into a log

The builder only *describes* a log. `Triton` decides what happens to it:

| Call                                                                            | Effect                                                                |
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `Triton.log(builder)`                                                           | **Buffer** at the builder's level. Nothing published until `flush()`. |
| `Triton.error(builder)` (also `warning`/`info`/`debug`/`fine`/`finer`/`finest`) | Set that level, then buffer.                                          |
| `Triton.logNow(builder)`                                                        | Buffer **and** flush this one immediately.                            |

{% code lineNumbers="true" %}

```apex
// Buffered — cheap, batched, published on flush()
Triton.log(Triton.makeBuilder()
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.Community)
    .summary('Sample log entry')
    .details('A detailed description of what happened')
    .level(TritonTypes.Level.INFO));

// Immediate — published on the spot
Triton.logNow(Triton.t.summary('Critical failure').exception(e));
```

{% endcode %}

{% hint style="info" %}
`Triton.log(...)` **buffers**; `Triton.logNow(...)` **publishes now**. Reach for `logNow` only when a log must survive even if the transaction dies. Everything else buffers and flushes at a boundary — see [Buffering, Flushing & Deferred Publishing](/core-concepts/buffering-flushing.md).
{% endhint %}

## The method surface

The builder is broad, but it clusters into a handful of groups.

| Group                     | Methods                                                                                                                                 |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Classification**        | `.category(...)` · `.type(...)` · `.area(...)` · `.level(...)`                                                                          |
| **Level shorthands**      | `.error()` · `.warning()` · `.info()` · `.debug()` · `.fine()` · `.finer()` · `.finest()` (each also has a `(String summary)` overload) |
| **Content**               | `.summary(String)` · `.details(String)` · `.stackTrace(String)` · `.operation(String)` · `.action(String)`                              |
| **Errors & DML**          | `.exception(Exception)` · `.dmlResults(List<Object>)`                                                                                   |
| **Records**               | `.relatedObject(Id/String)` · `.relatedObjects(...)` · `.userId(Id)`                                                                    |
| **Integrations**          | `.integrationPayload(HttpRequest, HttpResponse)` · `.integrationPayload(RestRequest, RestResponse)`                                     |
| **Limits & context**      | `.limitInfo()` · `.limitInfoLite()` · `.withFullLimitInfo()` · `.withoutLimitInfo()` · `.apexExecutionContext()`                        |
| **Tracing**               | `.spanId(String)` · `.parentSpanId(String)` · `.traceable(Boolean)` · `.tracePoint()` · `.transactionId(String)`                        |
| **Issues & escape hatch** | `.createIssue()` · `.attribute(String, Object)`                                                                                         |

Every method returns the builder, so it all chains. A few deserve a closer look.

### `.exception(e)` — the error one-liner

Don't hand-set type, summary, stack trace, and details for an error. `.exception(e)` pulls the type from the exception (falling back to `Backend`), sets the level to `ERROR`, copies the message into the summary, captures the stack trace and details, and flags the log for issue creation:

{% code lineNumbers="true" %}

```apex
try {
    chargeCard(payment);
} catch (Exception e) {
    Triton.error(Triton.t.area(TritonTypes.Area.OpportunityManagement).exception(e));
}
```

{% endcode %}

### `.dmlResults()` — partial-success DML

Running partial-success DML? Hand the results straight to the builder. It tallies successes and failures, collects the failed ids, formats the error messages, and only produces a log **if something actually failed**:

{% code lineNumbers="true" %}

```apex
List<Database.SaveResult> results = Database.insert(accounts, false);
Triton.log(Triton.t
    .area(TritonTypes.Area.Accounts)
    .summary('Bulk account insert')
    .dmlResults(results));
```

{% endcode %}

It understands `SaveResult`, `DeleteResult`, `UpsertResult`, `UndeleteResult`, and `MergeResult`, sets the type to `DMLResult`, relates the failed records, and logs at `ERROR`.

### `.integrationPayload()` — HTTP and REST exchanges

Serialize a full request/response pair onto the record for the payload viewer. There's an overload each for outbound callouts and inbound REST:

{% code lineNumbers="true" %}

```apex
builder.integrationPayload(httpRequest, httpResponse);   // outbound callout
builder.integrationPayload(restRequest, restResponse);   // inbound REST endpoint
```

{% endcode %}

See [Integration Logs](/common-apex-usage-patters/integration-logs.md) and [Apex REST Logging](/common-apex-usage-patters/apex-rest-logging.md) for the full patterns.

### `.relatedObject()` — link records

Associate one or many records so the log surfaces in the Related Objects view:

{% code lineNumbers="true" %}

```apex
builder.relatedObject('001xx000003DGb2AAG');
builder.relatedObjects(new Set<Id>{ acc.Id, opp.Id });
```

{% endcode %}

Invalid ids are quietly skipped, so you can pass raw strings without pre-validating.

### Limit info and execution context

By default, buffered logs capture essential governor usage (SOQL, DML, CPU, heap, callouts). You can tune this:

{% code lineNumbers="true" %}

```apex
builder.withFullLimitInfo();   // also capture extended limits (SOSL, future, queueable, ...)
builder.withoutLimitInfo();    // skip limit capture entirely (e.g. high-volume trace logs)
```

{% endcode %}

`.apexExecutionContext()` appends the current context (trigger operation and sObject, or the async context type) to the details — Triton adds it automatically for Apex-category logs.

### Custom attributes — the escape hatch

Any field on `pharos__Log__c` — standard or custom — is reachable with `.attribute()`:

{% code lineNumbers="true" %}

```apex
Triton.log(Triton.t.summary('Order processed').attribute('Order_Number__c', orderNumber));
```

{% endcode %}

It works, but sprinkling API names through your code is fragile. **Prefer a typed method.** `TritonBuilder` declares its own field names as constants at the top of the class and adds a method per field — follow the same approach for your org:

{% code lineNumbers="true" %}

```apex
public static final String ORDER_NUMBER = 'Order_Number__c';

public TritonBuilder orderNumber(String value) {
    this.attribute(ORDER_NUMBER, value);
    return this;
}
```

{% endcode %}

Now the call site is clean, autocompletes, and has a single place to change the field name:

{% code lineNumbers="true" %}

```apex
Triton.log(Triton.t.summary('Order processed').orderNumber(orderNumber));
```

{% endcode %}

### Tracing helpers

`.spanId()`, `.parentSpanId()`, `.traceable(Boolean)`, and `.tracePoint()` support fine-grained, span-based tracing across a distributed process. They're covered end-to-end in [Spans & Tracing](/end-to-end/spans-and-tracing.md).

## Templates

Look across any of the examples above and the same fields recur — category, area, sometimes a related object. A **template** is a builder you configure once and reuse.

Set it where your unit of work begins:

{% code lineNumbers="true" %}

```apex
Triton.setTemplate(Triton.makeBuilder()
    .category(TritonTypes.Category.Apex)
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.Community));
```

{% endcode %}

Then start every log from it with `Triton.t`:

{% code lineNumbers="true" %}

```apex
Triton.info(Triton.t.summary('Fetched 40 records'));
Triton.error(Triton.t.summary('Sync failed').exception(e));
```

{% endcode %}

`Triton.t` is shorthand for `Triton.fromTemplate()`. Each call hands you a **fresh clone** stamped with the current transaction id — so shared fields come along, but per-log fields (summary, details, exception) never bleed from one log into the next.

### Templates in practice

Here's the [batch job from the previous page](/common-apex-usage-patters/batch-logging.md#grouping-every-iteration-under-one-transaction) leaning on a template — notice how each log only spells out what's unique:

{% code lineNumbers="true" %}

```apex
public class LeadsBatch implements Database.Batchable<SObject>, Database.Stateful {
    private final String query;
    private String transactionId;
    private Integer iteration = 0;

    public LeadsBatch(String leadQuery) {
        this.query = leadQuery;
        this.transactionId = Triton.startTransaction();
        Triton.setTemplate(Triton.makeBuilder()
            .type(TritonTypes.Type.Backend)
            .area(TritonTypes.Area.LeadConversion));
    }

    public Database.QueryLocator start(Database.BatchableContext bc) {
        Triton.resumeTransaction(transactionId);
        Triton.info(Triton.t.summary('LeadsBatch starting').details('Query: ' + query));
        Triton.flush();
        return Database.getQueryLocator(query);
    }

    public void execute(Database.BatchableContext bc, List<Lead> leads) {
        Triton.resumeTransaction(transactionId);
        iteration++;
        Triton.fine(Triton.t.summary('Executing iteration ' + iteration));
        // ... process leads ...
        Triton.flush();
    }

    public void finish(Database.BatchableContext bc) {
        Triton.resumeTransaction(transactionId);
        Triton.info(Triton.t.summary('LeadsBatch finished').details('Iterations: ' + iteration));
        Triton.flush();
    }
}
```

{% endcode %}

Why bother? **Consistency** (every log shares the same taxonomy), **less code** (specify only what changes), **maintainability** (change the shared fields in one place), and **readability** (the noise is gone). A little up-front effort for a lot less fragility later.

{% hint style="info" %}
**Why does `Triton.t` clone?** If it returned the same builder each time, one log's summary would leak into the next. Cloning makes the template a stamp, not a shared mutable object. A couple of per-log fields (the full summary text, captured execution context) are intentionally *not* cloned.
{% endhint %}
