关于 nebula-logger-instrumentation
这份技能指南介绍如何在 Salesforce 的 Apex、Lightning Web Components、Aura、Flow 和 OmniStudio 中使用 Nebula Logger。内容涵盖受支持的 API、日志结构化补充、持久化及异步事务关联。
主要功能
- 01覆盖 Apex、LWC、Aura、Flow 和 OmniStudio
- 02七种日志级别与事务场景
- 03补充记录、异常和标签
- 04关联异步事务的父子日志
- 05通过 CallableLogger 可选调用
适用场景
- 01为 Salesforce 业务逻辑添加结构化日志
- 02追踪相关的异步事务
- 03在无编译时依赖时调用 Nebula Logger
- 04在 Flow 或 OmniStudio 中配置日志操作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jongpie/ nebulalogger --skill nebula-logger-instrumentation安装后在 Claude Code 中输入/nebula-logger-instrumentation确认已加载。
详细介绍
使用 Nebula Logger 埋点代码
支持的 API 范围
Nebula Logger 支持的 API 范围包括 Apex 中所有标记为 global 的内容,以及从 LWC 模块 c/logger 导出的所有内容。托管包(命名空间为 Nebula)仅公开 global 范围;解锁包在技术上也公开 public 类和方法,因为没有命名空间边界阻止访问。
请勿在自己的代码中依赖 public Apex 类或方法。 它们是 Nebula Logger 的内部实现,可能在任何版本中发生更改、重命名或移除,且不会有弃用过渡期。LoggerDataStore、LoggerConfigurationSelector、LogEntryHandler、LoggerPlugin(类本身,而不是 LoggerPlugin__mdt 记录),以及所有其他未使用 global 修饰符的内容,都可能在不另行通知的情况下更改。
如果你需要的功能无法通过 global 范围使用,请在 https://github.com/jongpie/NebulaLogger/issues 提交问题,而不要调用 public 方法。本技能及其配套内容仅引用 global API。
核心模型
每个 Nebula Logger 运行时上下文都遵循相同模式:
- 添加一个或多个日志条目,并指定级别(
ERROR、WARN、INFO、DEBUG、FINE、FINER、FINEST)。 - 可选择为每个条目添加记录、异常详情或标签。
- 在事务结束时调用一次
saveLog(),持久化缓冲的条目。
本技能的其余部分将展示在各个运行时上下文中的用法。
通用概念
日志级别
每个上下文都支持以下七个级别:ERROR、WARN、INFO、DEBUG、FINE、FINER、FINEST。实际持久化哪些级别取决于用户生效的 LoggerSettings__c.LoggingLevel__c。有关层级设置,请参阅 nebula-logger-install;有关级别选择建议,请参阅 nebula-logger-best-practices。
场景和标签
场景用于按业务流程粗略归类整个事务;标签则用于精细筛选单个条目。所有运行时环境都支持这两者。
- 使用
setScenario('Order Fulfillment')设置一次事务场景。 - 为每个条目添加标签:在特定级别的调用后链式调用
.addTag('inventory')。
记录关联
优先使用结构化记录关联(setRecord(...)),而不是将 ID 写入消息字符串。Nebula Logger 会存储记录的 JSON 快照,以便日志页面显示记录在创建条目时的状态。
异步事务关联
异步工作(批处理、可排队任务、计划任务)会在新的 Apex 事务中运行,默认会生成一个无关联的 Log__c。若要将子事务关联回父事务:
- 使用
Logger.getTransactionId()获取父事务的 ID。 - 将其传递给子事务,然后在子事务开始时调用
Logger.setParentLogTransactionId(parentId)。
这样会在两个 Log__c 记录之间创建 ParentLog__c 关联,使调查过程可以沿着关联链进行追溯。
Apex
使用特定级别的方法添加条目,然后调用一次 Logger.saveLog() 进行持久化。
public with sharing class InvoiceService {
public static void processInvoice(Id invoiceId) {
try {
Logger.info('Starting invoice processing for ' + invoiceId);
Logger.debug('Loading invoice and related records');
// Business logic here
Logger.info('Invoice processing completed successfully');
} catch (System.Exception ex) {
Logger.error('Invoice processing failed', ex);
throw ex;
} finally {
Logger.saveLog();
}
}
}
异常日志记录
Logger.exception(...) 会在一次调用中写入包含完整异常上下文的 ERROR 日志条目、保存日志并重新抛出异常。之后不要再单独调用 Logger.saveLog() 或 throw ex;——这两者都是多余的(而且 throw 不会执行,因为 Logger.exception(...) 已经抛出了异常)。
try {
update accountsToUpdate;
} catch (System.DmlException ex) {
Logger.exception('Failed to update accounts', ex);
}
如果你想记录异常,但希望单独决定是否重新抛出,请使用 Logger.error(message, ex),并显式调用 Logger.saveLog() 和 throw:
try {
update accountsToUpdate;
} catch (System.DmlException ex) {
Logger.error('Failed to update accounts', ex);
Logger.saveLog();
throw ex;
}
附加记录
Schema.Account acct = [SELECT Id, Name FROM Account WHERE Id = :accountId LIMIT 1];
Logger
.warn('Credit check returned warnings')
.setRecord(acct)
.addTag('credit-check')
.addTag('customer-onboarding');
Logger.saveLog();
setRecord(...) 定义在 LogEntryEventBuilder 上(由 Logger.info(...) / Logger.warn(...) / 等方法返回),而不是定义在 Logger 本身上。全局重载包括 setRecord(SObject)、setRecord(Id)、setRecord(List<SObject>)、setRecord(Map<Id, SObject>)、setRecord(System.Iterable<Id>) 和 setRecordId(Id)。如上所示,从对应级别的调用开始链式调用这些方法。
异步父子关联
public with sharing class ParentQueueable implements System.Queueable {
public void execute(System.QueueableContext context) {
String parentTransactionId = Logger.getTransactionId();
Logger.info('Parent job started');
Logger.saveLog();
System.enqueueJob(new ChildQueueable(parentTransactionId));
}
}
public with sharing class ChildQueueable implements System.Queueable {
private final String parentLogTransactionId;
public ChildQueueable(String parentLogTransactionId) {
this.parentLogTransactionId = parentLogTransactionId;
}
public void execute(System.QueueableContext context) {
Logger.setParentLogTransactionId(this.parentLogTransactionId);
Logger.info('Child job linked to parent log transaction');
Logger.saveLog();
}
}
通过 CallableLogger 使用可选依赖
如果代码希望在安装了 Nebula Logger 时向其记录日志,但又不想产生硬依赖(例如,ISV 包希望在恰好安装了 Nebula Logger 的客户组织中发送丰富的遥测数据,而不要求每位客户都安装它),则应通过 System.Callable 动态调用 CallableLogger,而不是直接引用 Logger 类。
在未锁定包和托管包中,CallableLogger 都是 global,并将每种日志操作都作为字符串操作公开,输入为 Map<String, Object>。由于调用方在编译时不会引用 Logger,无论是否存在 Nebula Logger,调用方包都能顺利编译和安装。
public with sharing class OptionalLogger {
// An example of how to dynamically check first for the managed package ('Nebula' namespace), then the unlocked package (no namespace)
private static final System.Type CALLABLE_LOGGER_TYPE = System.Type.forName('Nebula', 'CallableLogger') ?? System.Type.forName('CallableLogger');
public static void info(String message) {
logEntry(System.LoggingLevel.INFO, message);
}
public static void error(String message, System.Exception apexException) {
Map<String, Object> input = new Map<String, Object>{
'loggingLevel' => System.LoggingLevel.ERROR.name(),
'message' => message,
'exception' => apexException,
'saveLog' => true
};
invoke('newEntry', input);
}
private static void logEntry(System.LoggingLevel level, String message) {
invoke('newEntry', new Map<String, Object>{ 'loggingLevel' => level.name(), 'message' => message, 'saveLog' => true });
}
private static void invoke(String action, Map<String, Object> input) {
if (CALLABLE_LOGGER_TYPE == null) {
return;
}
System.Callable logger = (System.Callable) CALLABLE_LOGGER_TYPE.newInstance();
logger.call(action, input);
}
}
调用 OptionalLogger.info(...) / OptionalLogger.error(...) 的代码,在安装了 Nebula Logger 的组织中会记录日志,在未安装的组织中则不执行任何操作——不会出现运行时错误,也没有编译时依赖,客户无需执行安装后步骤。
CallableLogger 支持的操作
每种操作都接受 Map<String, Object>,并返回 Map<String, Object> 输出(包含 isSuccess、transactionId、parentLogTransactionId、requestId 键,以及失败时的错误详情)。
| 操作 | 用途 |
|---|---|
newEntry | 添加日志条目。支持下表中的所有增强键。 |
saveLog | 持久化缓冲的条目。可选接受 saveMethodName。 |
getTransactionId | 返回当前事务 ID(用于异步父子关联)。 |
getParentLogTransactionId / setParentLogTransactionId | 读取 / 设置父事务 ID。 |
getScenario / setScenario / endScenario | 读取 / 设置 / 结束事务的场景。 |
tryCatch | newEntry 的简写,使用 loggingLevel=ERROR 和序列化输入消息;适用于通用 catch 块。 |
newEntry 支持的输入键
上文中的 ISV 模式和 OmniStudio 的 Remote Action 步骤使用相同的输入键。
| 键 | 用途 |
|---|---|
loggingLevel | 级别名称(INFO、ERROR、WARN、DEBUG、FINE、FINER、FINEST)。 |
message | 条目消息。 |
saveLog | 设为 true 可在添加条目后立即持久化。 |
saveMethodName | 覆盖本次调用使用的保存方法(EVENT_BUS、QUEUEABLE、REST、SYNCHRONOUS_DML)。 |
tags | 标签名称的 List<String>。 |
exception | 要附加的 System.Exception 实例。 |
recordId / record / recordList / recordMap | 记录关联,格式与 Apex 的 setRecord(...) 重载相同。 |
scenario | 此事务的场景。 |
parentLogTransactionId | 用于异步关联的父事务 ID。 |
LWC
从 c/logger 模块导入 getLogger,并在每个组件实例中调用一次。
import { LightningElement } from 'lwc';
import { getLogger } from 'c/logger';
export default class PaymentPanel extends LightningElement {
logger = getLogger();
connectedCallback() {
this.logger.setScenario('Payment UI');
this.logger.info('Payment panel initialized');
this.logger.saveLog();
}
async handleSave() {
try {
this.logger.debug('Submitting payment request');
// async work
this.logger.info('Payment submitted');
} catch (error) {
this.logger.error('Payment submit failed').setExceptionDetails(error);
} finally {
await this.logger.saveLog();
}
}
}
getLogger()返回一个作用域化的日志记录器实例。调用一次并重复使用。saveLog()是异步的——如果外围代码需要在返回前完成保存,请使用await。- 级别方法会返回一个构建器,支持
.setRecord(...)、.setExceptionDetails(...)、.setScenario(...)和.addTag(...)。
Aura
在标记中嵌入 <c:logger> 组件,并通过 component.find('logger') 访问它。
<aura:component>
<c:logger aura:id="logger" />
</aura:component>
({
handleRender: function (component) {
const logger = component.find('logger');
logger.info('Aura component initialized');
logger.saveLog();
},
logButtonClick: function (component, event) {
const logger = component.find('logger');
logger.info('Save button clicked').addTag('user-action');
logger.saveLog();
}
});
从 handleRender 或用户触发的处理程序中访问日志记录器,而不是从 doInit 中访问。嵌入的 <c:logger> 是一个 LWC-on-Aura 组件,而 doInit 触发时,LWC 子组件的方法尚不可用。如果必须在初始化时运行监测代码,请将其包装在 setTimeout(..., 0) 中,使其在渲染周期后运行。
Flow
Nebula Logger 提供三个可调用操作(均属于 Logging 类别)和一个保存操作:
Add Log Entry- 添加包含消息和日志级别的普通日志条目。Add Log Entry for an SObject Record- 添加与单条记录关联的条目。Add Log Entry for an SObject Record Collection- 添加与记录集合关联的条目。Save Log- 持久化缓冲的条目。每条添加了条目的 Flow 路径末尾都要使用此操作。
Flow 变量引用会通过标准合并语法({!recordId}、{!Account.Name} 等)传入可调用输入。为任意 Add Log Entry* 操作设置场景,即可将整个 Flow 的条目归入同一个业务流程。
OmniStudio
OmniStudio 的 OmniScripts 和 Integration Procedures 通过与 Apex 部分“可选依赖”小节中所述的同一个 CallableLogger 类记录日志。无需编写 Apex,只需在 OmniScript / IP 中配置一个 Remote Action 步骤:
Remote Class:如果已安装托管包,则使用Nebula.CallableLogger;否则使用CallableLogger。Remote Method:使用newEntry添加条目(也可在同一次调用中保存),或使用saveLog持久化待处理条目。完整选项请参阅上方 Apex 部分中的“支持的操作”和“支持的输入键”表格。Additional Input:以声明式方式传入输入键。输入映射中超出已知键的内容会作为“OmniStudio Input”区块追加到条目的消息中,因此特定流程的上下文无需额外处理即可进入日志。
CallableLogger 会自动设置 OriginType__c = 'OmniStudio',并从 OmniStudio 的默认输入中获取 omniProcessId,使控制台筛选结果能清楚区分来自 OmniStudio 的条目。
保存方法选择指南
Logger.SaveMethod 的取值为 EVENT_BUS、QUEUEABLE、REST 和 SYNCHRONOUS_DML。
| 保存方法 | 适用场景 | 权衡 |
|---|---|---|
EVENT_BUS(默认) | 通用应用日志记录 | 受平台事件容量限制 |
QUEUEABLE | 需要延后执行任务并降低同步 CPU 压力时 | 增加异步依赖,并受队列执行时机影响 |
REST | 需要避免当前上下文中的混合 DML 限制时 | 需要调用路径和有效的会话上下文 |
SYNCHRONOUS_DML | 需要立即持久化且能接受回滚风险时 | 如果事务失败,日志插入也会回滚 |
可通过 LoggerSettings__c.DefaultSaveMethod__c 全局覆盖默认值,也可通过 Logger.saveLog(Logger.SaveMethod.QUEUEABLE) 按事务覆盖。
相关技能
- nebula-logger-testing-your-code - 测试调用上述 API 的代码。
- nebula-logger-console - 浏览这些 API 生成的日志记录。
- nebula-logger-best-practices - 团队级日志埋点标准。