# Apex Logging Basics

Now that we've completed all the preliminary work, let's get going! We'll cover the basics of what's included in the logging tools and look at some examples.

## Instantiating a Logger

Pharos Triton utilizes a Singleton pattern for accessing its capabilities. This is a handy way of ensuring that the state is preserved throughout the entire transaction. It also cuts down on the verbosity of creating a new class from scratch by referencing a static instance instead. We think that you will like this approach. However, if you don't, you can remove the Singleton implementation and switch over to the standard usage pattern.

Below is an example of how logging methods can be invoked.

```apex
try {
....
} catch (Exception e) {
    Triton.instance.error(TritonTypes.Area.Opportunities, e);
}
```

{% hint style="info" %}
If you decide to remove the singleton usage, take care to observe the use cases around bulk logging.
{% endhint %}

## Default Logging Methods

Pharos Triton provides a variety of logging methods to give you plenty of options for how you pass data to your log records. All of these reside inside *Triton.cls.* Below are a few examples:

```apex
public void error(Area area, Exception e)
public void addError(Area area, Exception e)

public void debug(Type type, Area area, String summary, String details)
public void addDebug(Type type, Area area, String summary, String details)

public void event(Level level, Type type, Area area, String summary, String details)
public void addEvent(Type type, Area area, String summary, String details)

.....
```

Many methods are overloaded to accept varying sets of parameters. You can log as much as you want or as little as you want.

### Instantaneous vs Buffered

The logging methods are divided into two major categories: **instantaneous** and **buffered**.

The first category immediately persists the logs. In other words, the methods in this category will build the log record, buffer it, and publish the platform event.

The second category of methods simply stashes the logs into a buffer until the *flush()* method is called. All methods in this second category are prefixed with "*add".*

For example this method will instantly publish an error log event:

```apex
public void error(TritonTypes.Area area, Exception e)
```

This method will buffer an error log record without publishing:

```apex
public void addError(TritonTypes.Area area, Exception e)
```

You'll notice that most methods have a corresponding *add\** version. In fact, the instantaneous methods will utilize their *add* counterpart, followed by a *flush()*. Here's an example of an instantaneous error method:

```apex
public void error(TritonTypes.Area area, Exception e) {
    addError(area, e);
    flush();
}
```

### Saving a Log

In order to save a log there are three steps involved:

1. Build the log, using Pharos *builder.*
2. Append the log to the buffer.
3. *flush()* when ready.

The last step will publish the platform event. Using the default methods [mentioned above](#default-logging-methods) will take care of all these steps for you. It's good practice to follow this pattern for your own logging methods as well.

Let's examine more closely what happens inside one of the default methods. The following method will create an error log and add it to the log buffer:

```apex
public void addError(TritonTypes.Area area, Exception e) {
    add(                
         makeBuilder()
         .category(TritonTypes.Category.Apex)
         //use exception type, Backend if blank
         .type(String.isBlank(e.getTypeName()) ? TritonTypes.Type.Backend.name() : e.getTypeName())
         .area(area)
         .summary(e.getMessage())
         .stackTrace(e.getStackTraceString())
         .details(String.valueOf(e) + SPACE_SEP + e.getStackTraceString())
         .transactionId(TRANSACTION_ID)
         .relatedObjects(relatedObjectIds)
         .level(TritonTypes.Level.ERROR)
         .createIssue()
         .build());
}
```

Few things to note here:

* The use of the Category, Type and Area enums within the TritonTypes class. These indicate that this log is to be presented as an error with a specific type and functional area. This is covered in more detail in the [enums section](#important-enums).
* The use of the *makeBuilder()* method and the builder pattern for constructing logs. This is covered in [more detail here](#id-01hswfnenxm0yhrnat382sq96g).
* The use of the *.attribute()* method that allows setting of standard and custom fields on Log records. This is addressed further in [this section](#h_01hsz0123tet1q4avq0hjzy4wp).
* The use of the *add()* method to buffer the log.

You'll find all of the default logging methods adhering to the same pattern to various degrees. We encourage you to explore this approach and adapt it for your own needs.

[This article](/pharos-triton/methods-reference/apex-methods-reference.md) details the full list of logging methods.

### Bulk Logging

As you know, the name of the game on the Salesforce platform is "bulkification." Wherever possible, all operations are performed in bulk. There are multiple reasons for the platform to require bulk processing. When it comes to logging, though, there's one main reason: platform event (PE) allocations. In other words, there is a limit on how many platform events can be emitted. For more details on platform event limits, please refer to the [Salesforce documentation here.](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_event_limits.htm)

If your org is consuming a large number of PE allocations, the added logging will impact this limit as well. Therefore, it is more important to log in bulk by buffering as many logs as possible before calling the *flush()* method. In general, the same best practices apply here as for DML operations. There is a finer balance to strike here, however. If all logs are buffered, in the event of an error or exception the stashed logs may never get created. The code between logging statements will need to be carefully examined for exception possibilities. If there is a risk of an exception, it's best to *flush()* logs before moving on.

## Important Enums

There are three default enums that come with Pharos Triton. They can be found inside the *TritonTypes.cls*. The purpose of these is to provide a consistent list of values for three key fields: Category, Type and Functional Area.

Why Enums? Technically, any string value can be written into the three fields mentioned above. However, for clarity purposes, it's a good idea to keep these values explicit. When new values are introduced, they should be added to the enums accordingly. This will help you with code maintenance and readability.

{% hint style="info" %}
We highly encourage you to **add your own** enum values, especially for the Area enum. These values are meant to represent accurate functional areas in the context of *your* org. There is simply no one-size-fits-all here so feel free to expand your enums value sets.
{% endhint %}

### Category <a href="#h_01hsyh27q519ca5zkjbdp2javx" id="h_01hsyh27q519ca5zkjbdp2javx"></a>

This value represents a broad category of a log or issue. These values are predefined by Pharos and it's a good idea to stick to the value set that comes out of the box. The full list is as follows:

* Apex
* Integration
* LWC
* Aura
* Bulk API
* Flow
* Process Builder
* Debug
* Error
* Event
* Warning
* Email-to-Case
* Web-to-Lead

Many methods within Pharos Triton will automatically set a category for you.

The main reason to stick with these predefined values is to ensure that Log and Issue detail pages are rendered with the appropriate lightning page layout and debug views. While Pharos Triton allows for lots of flexibility when it comes to populating your custom logs with data, mislabeling a Category on a log record can result in a different view being displayed. For example, if you label an Apex log with a Flow category, the resulting log record will still contain all the appropriate data, but the detail page will not show the Apex stack trace.

The general guideline is to stick to the specific technology where logging is performed. If you are logging from Apex, set the category as Apex. If you are logging in Flows, leave the default Flow category and so on.

The following values are always set automatically as part of Pharos' automated error capture:

* Bulk API
* Flow
* Process Builder
* Email-to-Case
* Web-to-Lead

It's best to stick to these category values for your custom logging needs as well. This will ensure that the correct view is presented and the relevant fields are visible

* Apex
* Flow
* LWC
* Aura
* Integration
* Error
* Event
* Warning
* Debug

### Type <a href="#h_01ht0g8b2jq5s214ya8nw8x9za" id="h_01ht0g8b2jq5s214ya8nw8x9za"></a>

Type provides a more specific technical classification. This value will be written to the Type field. For your custom logs, utilize this value as you see fit. This value assumes more freedom when it comes to the possible values and doesn't influence the view of a log record. Types are set by default during Pharos' automated error capture. For example, the default behavior of Pharos apex logs is to set the *Type* to the corresponding Exception type, such as DmlException. For flows, Type is set according the particular kind of flow that is utilized (e.g, Screen Flow or Autolaunched Flow).\
The default values included in Triton are:

* Backend
* Frontend

### Area <a href="#h_01hsyh0nmy90sa21hdbktchavj" id="h_01hsyh0nmy90sa21hdbktchavj"></a>

Area represents *Functional Area* at the Log and Issue level. This goal of this value is to convey a business or data centric description of functionality. For example, let's say there's an Account trigger that you would like to add logging to. In this case it would be appropriate to introduce an Area of Accounts. Alternatively, if there's a more specific operation that is performed in the Account trigger, such as data enrichment or an integration that pushes new Accounts into an external system, you could consider utilizing a Functional Area such as *AccountEnrichment* or *AccountSync.*

### Level <a href="#h_01hsyh0nmy90sa21hdbktchavj" id="h_01hsyh0nmy90sa21hdbktchavj"></a>

This enum represents log levels. These are typically used by *\*event()* and *\*debug()* logging methods to indicate a custom log level. Use this enum to control the verbosity of your logging output. Values are listed from the least verbose to the most verbose. For example if org logging level is set to INFO, any logs created with ERROR, WARNING or INFO levels will get saved and all others will not.

* ERROR
* WARNING
* INFO
* DEBUG
* FINE
* FINER
* FINEST

## LogBuilder <a href="#id-01hswfnenxm0yhrnat382sq96g" id="id-01hswfnenxm0yhrnat382sq96g"></a>

Pharos Triton makes use of the Builder design pattern. There's lots of information out there about this approach. [Here is as an example.](https://refactoring.guru/design-patterns/builder)

In short, the builder paradigm allows for an easy-to-understand, self-documenting approach to passing data to your log files. Here is an example of what Pharos Triton does to create a log record:

```apex
makeBuilder()
    .category(TritonTypes.Category.Debug)
    .type(type)
    .area(area)
    .summary(summary)
    .details(details)
.build());
```

Invoking the build function at the end returns a *pharos\_\_Log\_\_c* object with all the relevant fields populated. At this point the log is ready to either be buffered for later or persisted right away via the platform event.

One thing to note is the usage of the *makeBuilder()* method. This is simply a shorthand way of creating an instance of the Pharos builder class. It serves no other purpose than to cut down on the verbosity of creating a builder directly by referencing the *pharos* namespace.

```apex
public static TritonBuilder makeBuilder()
```

Please refer to [this section](/pharos-triton/methods-reference/apex-methods-reference/tritonbuilder.md) for the full API reference.

### Passing Custom Attributes to Logs <a href="#h_01hsz0123tet1q4avq0hjzy4wp" id="h_01hsz0123tet1q4avq0hjzy4wp"></a>

In the event you have a need to track additional custom attributes on the Log record, the *builder* offers a simple way of passing these values. In the example below, consider a custom field *My\_Custom\_Attribute\_\_c* on the Log record.

```apex
makeBuilder()
    .attribute('My_Custom_Attribute__c', customValue)
```

If you've already examined *TritonBuilder.cls* you might have noticed several static final constants declared at the very top, such as:

<pre class="language-apex"><code class="lang-apex"><strong>public static final String APEX_NAME = 'pharos__Apex_Name__c';
</strong>public static final String CREATED_TIMESTAMP = 'pharos__Created_Timestamp__c';
public static final String DURATION = 'pharos__Duration__c';
public static final String INTERVIEW_GUID = 'pharos__Interview_GUID_External__c';
......
</code></pre>

These are used by various default logging methods to set custom log attributes. It's a good idea to follow this practice for your own custom attributes as well.

First declare a static final constant, and then reference it:

```apex
public static final String MY_CUSTOM_ATTRIBUTE = 'My_Custom_Attribute__c';
....
makeBuilder()
    .attribute(MY_CUSTOM_ATTRIBUTE, customValue)
....
```

It's a good practice to keep things organized and keep a list of Log field dependencies all in one place.

#### Creating Custom Methods in TritonBuilder

While using `.attribute()` is functional, a better approach is to add custom methods directly to the `TritonBuilder` class. This provides a cleaner, more readable API and ensures type safety for your custom attributes.

For example, instead of using:

```apex
makeBuilder()
    .attribute(MY_CUSTOM_ATTRIBUTE, customValue)
```

You can add a custom method to `TritonBuilder`:

```apex
/**
 * Set custom attribute for tracking specific business logic
 * @param value -- String value for the custom attribute
 * @return this builder instance
 */
public TritonBuilder myCustomAttribute(String value) {
    this.builder.attribute(MY_CUSTOM_ATTRIBUTE, value);
    return this;
}
```

Then use it in your logging code:

```apex
makeBuilder()
    .category(TritonTypes.Category.Apex)
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.Accounts)
    .myCustomAttribute('Custom Value')
    .summary('Operation completed')
    .build()
```

This approach offers several benefits:

* **Type Safety**: You can enforce the correct data type for the attribute
* **Documentation**: JSDoc comments provide clear documentation for the method's purpose
* **Readability**: The method name clearly indicates what attribute is being set
* **IDE Support**: Better autocomplete and IntelliSense support
* **Consistency**: Follows the same fluent interface pattern as other builder methods

When adding custom methods to `TritonBuilder`, follow the established patterns:

* Return `this` to maintain the fluent interface
* Use descriptive method names that clearly indicate the attribute being set
* Include JSDoc documentation explaining the parameter and return value
* Consider adding overloaded methods for different parameter types if needed


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://triton.pharos.ai/pharos-triton/apex-logging-basics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
