mcpField
N
nebula-logger-instrumentation

nebula-logger-instrumentation

github.com/jongpie开发97485 次安装9 周前更新
代码仓库

关于 nebula-logger-instrumentation

这份技能指南介绍如何在 Salesforce 的 Apex、Lightning Web Components、Aura、Flow 和 OmniStudio 中使用 Nebula Logger。内容涵盖受支持的 API、日志结构化补充、持久化及异步事务关联。

主要功能

  1. 01覆盖 Apex、LWC、Aura、Flow 和 OmniStudio
  2. 02七种日志级别与事务场景
  3. 03补充记录、异常和标签
  4. 04关联异步事务的父子日志
  5. 05通过 CallableLogger 可选调用

适用场景

  1. 01为 Salesforce 业务逻辑添加结构化日志
  2. 02追踪相关的异步事务
  3. 03在无编译时依赖时调用 Nebula Logger
  4. 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 运行时上下文都遵循相同模式:

  1. 添加一个或多个日志条目,并指定级别(ERROR、WARN、INFO、DEBUG、FINE、FINER、FINEST)。
  2. 可选择为每个条目添加记录、异常详情或标签。
  3. 在事务结束时调用一次 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读取 / 设置 / 结束事务的场景。
tryCatchnewEntry 的简写,使用 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) 按事务覆盖。

相关技能

评论

支持 Markdown