> 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/core-concepts/log-levels.md).

# Log Levels

How Triton log levels work — the severity scale, choosing the right level, and tuning verbosity per environment with Log\_Level\_\_mdt instead of code changes.

A **level** answers one question about a log: how important is it? Triton uses levels for two things — telling humans how urgent a record is, and deciding at runtime whether a log is even worth saving. The second part is the powerful one: you can turn the firehose up in a sandbox and down in production **without changing a line of code**.

## The scale

There are seven levels, ordered from least to most verbose:

| Level                       | Use it for                                                                                               |
| --------------------------- | -------------------------------------------------------------------------------------------------------- |
| `ERROR`                     | Something failed and needs attention — exceptions, failed callouts, broken invariants.                   |
| `WARNING`                   | Something recoverable or suspicious — retries, partial DML failures, deprecated paths.                   |
| `INFO`                      | Notable business events — "order placed", "sync completed". The default signal you'd want in production. |
| `DEBUG`                     | Developer-oriented detail useful while diagnosing.                                                       |
| `FINE` / `FINER` / `FINEST` | Increasingly chatty tracing — payload dumps, loop-level detail, everything-and-the-kitchen-sink.         |

Set the level on the builder, either explicitly or with the shorthand method:

{% code lineNumbers="true" %}

```apex
Triton.log(Triton.t.summary('Order placed').level(TritonTypes.Level.INFO));
Triton.info(Triton.t.summary('Order placed'));   // identical
```

{% endcode %}

{% hint style="info" %}
Levels are ordered from least verbose (`ERROR`) to most verbose (`FINEST`). "Turning the level down to INFO" means: keep `ERROR`, `WARNING`, and `INFO`; drop everything more verbose. This is the same mental model as Salesforce's own debug log levels.
{% endhint %}

## Runtime filtering with `Log_Level__mdt`

Here's where levels earn their keep. Triton reads a custom metadata type, **`Log_Level__mdt`**, to decide which logs actually get persisted. Each record sets a **threshold** (a level) for some slice of your logs, scoped by any combination of five fields: **Category**, **Type**, **Area**, **Component**, and **User**.

The rule for a single record is simple: **a log is saved when its level is at or below that record's threshold.** Turn the threshold down and the chatty logs silently stop persisting; turn it up and they come back — all through metadata, deployable per environment.

### An example

Say you create one `Log_Level__mdt` record:

| Field    | Value  |
| -------- | ------ |
| Category | `Apex` |
| Level    | `INFO` |

Now, for any `Apex` log:

* `ERROR`, `WARNING`, `INFO` logs → **saved** (at or below the `INFO` threshold)
* `DEBUG`, `FINE`, `FINER`, `FINEST` logs → **dropped**

Bump that record to `FINEST` in your sandbox and every level flows through; drop it to `WARNING` in production and only warnings and errors persist. No code change, no redeploy of Apex.

### Scoping a rule

A record's scope fields decide *which* logs it applies to. A record **targets** a log only when **every** field it fills in matches that log; any field left blank is a wildcard that matches everything. A record with only `Category = Apex` set therefore applies to all Apex logs, while one that also sets `Component` and `User` applies to a single component for a single user.

| Field         | Matches against                     | Notes                                                                                                                                                                                      |
| ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Category**  | The log's Category                  | e.g. `Apex`, `LWC`, `Flow`. Matched case-insensitively.                                                                                                                                    |
| **Type**      | The log's Type                      | e.g. `Backend`. Matched case-insensitively.                                                                                                                                                |
| **Area**      | The log's Area                      | Your functional area, e.g. `Community`. Matched case-insensitively.                                                                                                                        |
| **Component** | The component that produced the log | The Apex **class name**, the **LWC component name**, or the **Flow API name** — whichever matches the log's origin. Matched case-insensitively, but in full: `My_Flow-4` is not `My_Flow`. |
| **User**      | The running user                    | The 15-character Salesforce User Id. The one **case-sensitive** field — matching is exact on the 15-character form.                                                                        |

{% hint style="info" %}
**Component resolution.** The component is the Flow API name for Flow logs, the component name for LWC logs, and the class name for Apex logs. When an Apex log has no explicit operation, Triton derives the class from the stack trace so component-scoped rules still match — it only does this extra work when at least one component-scoped rule actually exists, so orgs without one pay nothing for it.
{% endhint %}

Matching on **Category**, **Type**, **Area** and **Component** is case-insensitive — they're free-text fields typed by hand, so `apex` and `Apex` mean the same thing. **User is the exception:** Salesforce Ids are case-sensitive base-62, and folding their case would collide distinct users, so a User Id is compared exactly, on its 15-character form.

{% hint style="warning" %}
**Case-insensitive matching silently widens rules you already have.** A rule field typed `apex` — which previously matched nothing, because the log's Category is `Apex` — now matches every Apex log. Review 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, and it may be more specific than the rule you thought was in charge.
{% endhint %}

Case is the only thing matching is forgiving about, because **the scope fields are free text, not picklists**. On `Log_Level__mdt`, `Category__c`, `Type__c`, `Area__c` and `Component_Name__c` are `Text(255)` and `User_Id__c` is `Text(15)` — only **Level** is a picklist. Nothing validates a scope value against anything real, so a rule keyed on a value that no longer exists — a renamed class, a retired Area, a Category removed in 2.0 — doesn't error. It silently stops matching, and that slice of logs falls back to whatever broader rule applies. When you rename or retire a value, re-key the rules that name it.

### How multiple rules combine

When several records target the same log, the **single most specific one wins** — and its threshold overrides the others in *both* directions. A narrow rule can raise verbosity for its slice above a broad default, or **lower** it below one, so you can turn a single noisy component or user down while leaving everything else untouched.

Specificity is ranked by which fields a rule fills in, from narrowest to broadest scope:

**User > Component > Area > Type > Category**

Naming a narrower field always outranks any combination of broader ones — a rule that sets only `User` beats a rule that sets `Component + Area + Type + Category`. If two rules are *equally* specific (they fill in the same fields), the more restrictive (less verbose) level wins, so the outcome never depends on metadata order.

Two examples, both starting from a global default record at `INFO`:

* **Boost one slice.** Add `Area = RestAPI`, `Level = FINEST`. For `RestAPI` logs the more specific rule wins → every level flows through, while everything else stays at `INFO`.
* **Silence one slice.** Add `Component = NoisyController`, `Level = ERROR`. For that component the more specific rule wins → only `ERROR` persists, while everything else still logs at `INFO`.

{% hint style="success" %}
**Sensible defaults, no surprises.** If a log has no level set, it's always saved. And if none of the matching rules exist in `Log_Level__mdt` at all, the log is saved too. Filtering only kicks in once you've explicitly configured a threshold that covers the log — so adding Triton to an org never silently swallows logs until you opt into filtering.
{% endhint %}

{% hint style="success" %}
**Filtering fails open, and a bad rule can't break your transaction.** A malformed `Log_Level__mdt` row — one with a blank **Level**, for instance — is skipped rather than thrown into the transaction that was doing the logging, so your remaining rules still apply. And a log whose own level Triton can't parse is **kept**, not dropped. When filtering can't decide, the log survives.
{% endhint %}

## Choosing levels well

* **Reserve `ERROR` for things a human should look at.** If everything is an error, nothing is.
* **Make `INFO` your production signal.** Ask "would I want to see this in prod?" — if yes, it's probably `INFO` or higher.
* **Push volume down the scale.** Loop bodies, payload dumps, and step-by-step traces belong at `FINE` and below, where filtering can switch them off cheaply.
* **Don't gate logs with `if` statements.** Resist `if (debugMode) Triton.debug(...)`. Log at the right level and let `Log_Level__mdt` decide — that's the whole point.

## Related reading

* Classifying logs by [Category, Type & Area](/core-concepts/enums.md) — three of the fields that scope level filtering.
* [Log Levels: When to Use What](/triton-best-practices/log-levels-and-usage.md) in Best Practices, for deeper guidance and examples.
