Building your own plugin
Writing a plugin is: pick the right interface, implement it, register it with a LoggerPlugin__mdt record, ship it.
Which interface
Section titled “Which interface”- Trigger plugin (
LoggerPlugin.Triggerable) - runs duringLoggerSObjectHandlerexecution. Use for enrichment, outbound notifications, and per-record automation onLogEntryEvent__e,Log__c,LogEntry__c, and the tag junctions. - Batch plugin (
LoggerPlugin.Batchable) - runs duringLogBatchPurgerexecution. Use for archival, custom purge policies, and metrics.
A plugin implements exactly one of the two.
Example: trigger plugin
Section titled “Example: trigger plugin”Suppose you want to auto-tag every ERROR entry with severity:actionable.
public with sharing class AutoSeverityTagPlugin implements LoggerPlugin.Triggerable { public void execute(LoggerPlugin__mdt configuration, LoggerTriggerableContext input) { if (input.sobjectType != Schema.LogEntry__c.SObjectType) { return; } if (input.triggerOperationType != System.TriggerOperation.BEFORE_INSERT) { return; } for (LogEntry__c entry : (List<LogEntry__c>) input.triggerNew) { if (entry.LoggingLevel__c == 'ERROR') { entry.Tags__c = (entry.Tags__c == null ? '' : entry.Tags__c + ',') + 'severity:actionable'; } } }}Example: batch plugin
Section titled “Example: batch plugin”Suppose you want to emit a platform event with counts of purged entries.
public with sharing class PurgeMetricsPlugin implements LoggerPlugin.Batchable { private Integer totalPurged = 0;
public void start(LoggerPlugin__mdt configuration, LoggerBatchableContext input) { this.totalPurged = 0; }
public void execute(LoggerPlugin__mdt configuration, LoggerBatchableContext input, List<SObject> scopeRecords) { if (input.sobjectType == Schema.LogEntry__c.SObjectType) { this.totalPurged += scopeRecords.size(); } }
public void finish(LoggerPlugin__mdt configuration, LoggerBatchableContext input) { EventBus.publish(new PurgeMetric__e(TotalPurged__c = this.totalPurged)); }}Register the plugin
Section titled “Register the plugin”Create a LoggerPlugin__mdt record. For the trigger plugin above:
<?xml version="1.0" encoding="UTF-8"?><CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" fqn="LoggerPlugin.AutoSeverityTag"> <label>Auto Severity Tag</label> <protected>false</protected> <values> <field>IsEnabled__c</field> <value xsi:type="xsd:boolean">true</value> </values> <values> <field>SObjectHandlerApexClass__c</field> <value xsi:type="xsd:string">AutoSeverityTagPlugin</value> </values> <values> <field>SObjectHandlerExecutionOrder__c</field> <value xsi:type="xsd:double">10</value> </values></CustomMetadata>For a batch plugin, populate BatchPurgerApexClass__c instead.
Plugin folder layout
Section titled “Plugin folder layout”If you’re shipping the plugin as its own package (rather than inline in an existing project), mirror the layout used by the shipped plugins:
nebula-logger/plugins/<name>/ plugin/ <name>/ classes/ # Apex classes (impl + tests) customMetadata/ # LoggerPlugin.<Name>.md-meta.xml objects/ # Any plugin-specific custom objects/fields permissionsets/ # <Name>PluginAdmin.permissionset-meta.xml tests/ # Additional test artifacts (test suite, test permsets) README.mdThe LoggerPlugin.<Name>.md-meta.xml file is the registration record.
Testing plugins
Section titled “Testing plugins”- Write a
<PluginClass>_Tests.clsalongside the implementation underplugin/<name>/classes/. - Add the plugin’s test class to a Logger test suite so
npm run test:apex:suite:<name>covers it. - For trigger plugins: construct a
LoggerTriggerableContextmanually with the trigger records you want to exercise, then callexecute(configuration, input)directly. Do not require a real trigger fire in tests. - For batch plugins: construct a
LoggerBatchableContextand pass a synthesizedscopeRecordslist. Assert on side effects (DML, callouts viaSystem.Test.setMock, published events).
Ordering
Section titled “Ordering”- Trigger plugins run after the built-in
LoggerSObjectHandlerlogic for that object. - Multiple plugins on the same handler run in
SObjectHandlerExecutionOrder__corder (nulls last, then byDeveloperName). - Plugins that mutate records (adding tags, populating fields) should run before plugins that publish outbound notifications, so notifications see the enriched data.
New plugin checklist
Section titled “New plugin checklist”Before shipping:
- Apex class implements exactly one of
LoggerPlugin.Triggerable/LoggerPlugin.Batchable. LoggerPlugin.<Name>.md-meta.xmlexists, hasIsEnabled__c = true(orfalseif opt-in), and points at the class.<Name>PluginAdmin.permissionset-meta.xmlgrants full access on plugin-owned objects and CMDT.<PluginClass>_Tests.clscovers happy path, empty scope, and disabled-configuration cases.README.mdin the plugin folder documents install steps and any prerequisites.sfdx-project.jsonhas apackageDirectoriesentry for the plugin so it can be built as its own unlocked package.
Where next
Section titled “Where next”- Plugin framework overview - interface details.
- Existing plugins to read for reference: Slack, Big Object Archiving, Log Retention Rules, Async Failure Additions.