nebula-logger-instrumentation について
Salesforce の Apex、Lightning Web Components、Aura、Flow、OmniStudio で Nebula Logger を使うためのガイドです。対応 API、ログへの情報追加、保存、非同期トランザクションの関連付けを説明します。
主な機能
- 01Apex、LWC、Aura、Flow、OmniStudio に対応
- 027 種類のログレベルとシナリオ
- 03レコード、例外、タグの追加
- 04非同期処理の親子ログ連携
- 05CallableLogger による任意の呼び出し
活用シーン
- 01Salesforce の業務ロジックに構造化ログを追加
- 02関連する非同期トランザクションを追跡
- 03コンパイル時依存なしで Nebula Logger を呼び出す
- 04Flow または 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 の範囲で利用できない場合は、public メソッドにアクセスするのではなく、https://github.com/jongpie/NebulaLogger/issues に課題を報告してください。このスキルと関連するスキルでは、global API のみを参照します。
基本モデル
Nebula Logger のすべてのランタイムコンテキストは、同じパターンに従います。
- いずれかのレベル(
ERROR、WARN、INFO、DEBUG、FINE、FINER、FINEST)で、1 つ以上のログエントリを追加します。 - 必要に応じて、レコード、例外の詳細、タグを使って各エントリを充実させます。
- トランザクションの最後に一度だけ
saveLog()を呼び出し、バッファーにあるエントリを永続化します。
このスキルの以降の内容では、各ランタイムコンテキストでの実装方法を説明します。
共通の概念
ログレベル
すべてのコンテキストで、次の 7 つのレベルがサポートされています。ERROR、WARN、INFO、DEBUG、FINE、FINER、FINEST。実際に永続化されるレベルは、ユーザーに対して有効な LoggerSettings__c.LoggingLevel__c によって決まります。階層の設定については nebula-logger-install を、レベルの選択については nebula-logger-best-practices を参照してください。
シナリオとタグ
シナリオはビジネスプロセス単位でトランザクション全体を大まかにグループ化し、タグは個々のエントリを細かく分類します。どちらもすべてのランタイムでサポートされています。
setScenario('Order Fulfillment')でトランザクションのシナリオを一度設定します。- エントリごとにタグを付けます。レベル固有の呼び出しに
.addTag('inventory')をチェーンします。
レコードとの関連付け
メッセージ文字列に ID を埋め込むのではなく、構造化されたレコードの関連付け(setRecord(...))を優先してください。Nebula Logger はレコードの JSON スナップショットを保存するため、ログページにエントリ発生時点のレコードの状態を表示できます。
非同期トランザクションの関連付け
非同期処理(バッチ、キュー可能、スケジュール済み)は新しい Apex トランザクションで実行されるため、デフォルトでは関連のない Log__c が生成されます。子トランザクションを親に関連付けるには、次のようにします。
Logger.getTransactionId()で親のトランザクション ID を取得します。- その ID を子に渡し、子トランザクションの開始時に
Logger.setParentLogTransactionId(parentId)を呼び出します。
これにより、2 つの 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; を追加しないでください。どちらも冗長であり、Logger.exception(...) によってすでに例外がスローされているため、throw に到達することもありません。
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(...) は Logger 自体ではなく、LogEntryEventBuilder(Logger.info(...)/Logger.warn(...) などが返すオブジェクト)で定義されています。グローバルオーバーロードには、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 がインストールされている場合はログを記録しつつ、Nebula Logger に強く依存したくないコード(たとえば、Nebula Logger がインストールされている顧客組織で詳細なテレメトリを出力したいが、すべての顧客にインストールを要求したくない ISV パッケージ)は、Logger クラスを直接参照するのではなく、System.Callable 経由で CallableLogger を動的に呼び出してください。
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 | loggingLevel=ERROR とシリアライズされた入力メッセージを指定した newEntry の簡易版です。汎用的な 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をインポートし、コンポーネントインスタンスごとに1回呼び出します。
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()はスコープ付きロガーインスタンスを返します。1回呼び出し、再利用してください。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();
}
});
ロガーには、doInitではなく、handleRenderまたはユーザー操作に応じたハンドラーからアクセスしてください。埋め込まれた<c:logger>はLWCをAuraでラップしたもので、doInitの実行時にはLWCの子メソッドをまだ利用できません。初期化時に計測処理を実行する必要がある場合は、setTimeout(..., 0)でラップし、レンダリングサイクルの後に実行されるようにしてください。
Flow
Nebula Loggerには、3つの呼び出し可能なアクション(すべてLoggingカテゴリ)と、保存アクションが用意されています。
Add Log Entry- メッセージとログレベルを指定して、通常のログエントリを追加します。Add Log Entry for an SObject Record- 1つのレコードに関連付けたエントリを追加します。Add Log Entry for an SObject Record Collection- レコードコレクションに関連付けたエントリを追加します。Save Log- バッファーにあるエントリを永続化します。エントリを追加したすべてのフロー経路は、このアクションで終了させてください。
Flowの変数参照は、標準のマージ構文({!recordId}、{!Account.Name}など)を使って、呼び出し可能な入力に渡されます。各Add Log Entry*アクションにシナリオを設定すると、フロー全体のエントリを1つのビジネスプロセスとしてまとめられます。
OmniStudio
OmniStudio の OmniScripts と Integration Procedures は、Apex セクションの「Optional dependency」サブセクションで説明したものと同じ CallableLogger クラスを通じてログを記録します。Apex を記述する代わりに、OmniScript/IP で Remote Action ステップを設定します。
Remote Class:マネージドパッケージがインストールされている場合はNebula.CallableLogger、それ以外の場合はCallableLoggerを指定します。Remote Method:エントリーを追加するにはnewEntry(同じ呼び出しで保存することもできます)、保留中のエントリーを保存するにはsaveLogを指定します。選択肢の全容については、上記の Apex セクションにある「Supported actions」と「Supported input keys」の表を参照してください。Additional Input:入力キーを宣言的に渡します。入力マップ内の既知のキー以外の値は、エントリーのメッセージに「OmniStudio Input」ブロックとして追加されるため、追加の連携なしで、プロセス固有のコンテキストをログに反映できます。
CallableLogger は OriginType__c = 'OmniStudio' を自動的に設定し、OmniStudio のデフォルト入力から omniProcessId を取得します。これにより、コンソールでフィルタリングすると、OmniStudio 由来のエントリーを明確に識別できます。
保存方法の選択ガイド
Logger.SaveMethod の値は EVENT_BUS、QUEUEABLE、REST、SYNCHRONOUS_DML です。
| 保存方法 | 使用する状況 | トレードオフ |
|---|---|---|
EVENT_BUS(デフォルト) | 汎用的なアプリのログ記録 | プラットフォームイベントの容量に依存します |
QUEUEABLE | 処理を遅延させ、同期 CPU の負荷を軽減したい場合 | 非同期処理への依存と Queueable の実行タイミングが加わります |
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 - チーム全体で適用する計装の標準です。