Despite its strong capabilities, logging and observability remain one of the main weaknesses of Salesforce’s development environment. This becomes apparent when dealing with errors, warnings, and other issues in a Salesforce org. No system is perfect: even if it is designed perfectly, system errors will still occur due to user errors, external integration issues, etc.
When it comes to working with the native solutions that Salesforce offers, such as debug logs, they impose strict size and truncation limits. It needs to be enabled for each user and is automatically purged after some time. The standard System.debug() messages are limited to Apex code, and no equivalent exists on Flow Builder or Aura/LWC. These limitations can make it difficult to rely only on native tools, and a solution for helping with development, troubleshooting, and monitoring your Salesforce org has been much needed. This is where Nebula Logger comes in…
How Nebula Logger Solves the Problem
Nebula Logger is an open-source logging framework built as part of a larger project by Jonathan Gillespie. Due to its proven robustness and open-source nature, it has evolved into a community-driven project over the years.
Let’s start with a simple Nebula Logger scenario. Assume that you have a lead trigger or a record-triggered Flow on Lead that presets record fields, updates related records, and does an asynchronous callout to an external system.
Such cases may be error-prone, especially when the external integration returns an error. To catch any issues with the HTTP request or response, you would have to rely on System.debug() statements and native debug logs, which are purged after 24 hours.
Contrary to directly inserting log data through synchronous Apex, Nebula Logger makes use of the pub/sub model, using the custom LogEntryEvent__e platform event which mitigates the rollback risks. This structure also allows external systems to subscribe to any logging-related events, which can help in pulling your Salesforce logs stored in an external system.
How to Install and Set Up Nebula Logger
The recommended way is to install the package through its own GitHub page, as an unlocked or managed package.
Installing it as an unlocked package gives you control over the source code, which allows further customization, while a managed package offers AppExchange compatibility and allows automatic updates. Press the appropriate button (Sandbox or Production, unlocked or managed) and go through the installation steps as usual.
You can also use Salesforce CLI for installation by running the command line below. Note that [current version ID] will need to be replaced with the ID of the most recent version found on the GitHub page.
sf package install --wait 20 --security-type AdminsOnly --package [current version ID]
Nebula Logger comes with several permission sets. After installing the package, assign these to users who need access:
- Nebula Logger: Admin (LoggerAdmin) – full access.
- Nebula Logger: Log creator (LoggerLogCreator) – generate logs.
- Nebula Logger: Log viewer (LoggerLogViewer) – view all logs.
- Nebula Logger: End user (LoggerEndUser) – limited view and usage.
Ensure that users and automated processes (e.g., integrations) have the correct permissions.
After setting up the permission sets, navigate to Setup > Nebula Logger → Settings orSetup → Custom Settings → Logger Settings. Here you can make changes to default configurations, such as:
- Default log levels (e.g., INFO, ERROR).
- How logs are saved (e.g., event bus, queueable).
- How long to store the logs.
- Log visibility.
Once the installation and setup are done, you are now ready to use Nebula Logger in your org.
How to Put Nebula Logger to Use
If you are to use the Nebula Logger framework in Apex, you will be using the static logger class. The class stores each log entry, which is then inserted using the Logger.saveLog() statement.
Log entries can be registered using default Salesforce log levels: None, Error, Warn, Info, Debug, Fine, Finer, and Finest.

Log entries store a message (the exception message in most cases), but they can store additional variables, including but not limited to a record, a list of records, a record ID, or a Database.SaveResult, a list of Database.SaveResults, or an exception.
In case of the aforementioned lead trigger, each lead in Trigger.new or Trigger.new in its entirety can be the additional parameter.
A Log__c record is created for each transaction, with LogEntry__c for each entry registered through the logger class. For example, if you run this code through Anonymous Apex:
Logger.info('Info level logging');
Logger.error('Error level logging');
Logger.fine('Fine level logging related to an Account', [SELECT Id FROM Account WHERE Name = 'Edge Communications' LIMIT 1].Id);
Logger.saveLog();
A new Log__c record will be generated with three child LogEntry__c records, one for each statement that saves information using the logger class, like this:

The details section will give you more information about the transaction, such as the source IP, session type (where the transaction came from), and the source user. The record details of each log entry also reveal the line in which the log entry was registered, giving you a clearer view of the Apex code in action.
In the case of the lead trigger above, the exact progress of the transaction throughout the code, including the content of the request and response, can be tracked with enoFugh log entries.
If you run the Logger.saveLog() statement more than once in an Apex transaction, there will still be only one Log__c record as a result. However, it is still best practice to use Logger.saveLog() only once per transaction.
You can also add tags to your log entries, which will make them easy to filter. If you run this code through Anonymous Apex:
Logger.debug('Starting outbound REST API
callout').addTag('OutboundCallout');
Logger.saveLog();
A new LoggerTag__c record called OutboundCallout, to which the new log entry will be linked, will be upserted using the UniqueId__c field as its External ID. Relevant log entries can then be searched for using SOQL.

Hence, in the context of our lead trigger, we can imagine a scenario where each method in the trigger handler can have its own tag. This way, we will be able to pinpoint the exact code line and method where the execution succeeds or fails.
Nebula Logger is also available in Flows in the form of invocable actions. You can use three of the provided actions to register log entries:
- Add Log Entry: Adds an entry unrelated to any record.
- Add Log Entry for an SObject Record: Adds an entry related to a single record.
- Add Log Entry for an SObject Collection: Adds an entry related to multiple records.
After the log entries are registered, the Save Log action will have to be called, similar to how it is in Apex.

You can also use Nebula Logger in your LWC controller by importing the logger component.
import { getLogger } from 'c/logger';export default class LoggerTest extends LightningElement {
logger = getLogger();
connectedCallback() {
this.logger.debug('Connected callback called');
this.logger.saveLog();
}
}
Summary
This covers a basic introduction to the robust, open-source, and community-driven effort that is Nebula Logger.
If you’re looking for an advanced version of using Nebula Logger, be sure to check out the ever-growing wiki and this post by James Simone. Thank you for reading, and happy logging!

