ベストプラクティス
以下のベストプラクティスに従って、OpenTelemetry .NET のログを最大限に活用しましょう。
Logging API
ILogger
.NET は、アプリケーションの動作を監視し問題を診断するために、Microsoft.Extensions.Logging.ILogger インターフェイス(ILogger<TCategoryName> を含む)を通じて、高パフォーマンスな構造化ログをサポートしています。
パッケージバージョン
使用している .NET ランタイムのバージョンに関係なく、Microsoft.Extensions.Logging パッケージの最新の安定バージョンの ILogger インターフェイス(ILogger<TCategoryName> を含む)を使用してください。
- OpenTelemetry .NET SDK の最新の安定バージョンを使用している場合、
Microsoft.Extensions.Loggingパッケージのバージョンはパッケージの依存関係を通じてすでに管理されているため、気にする必要はありません。 - バージョン
3.1.0以降、.NET ランタイムチームはメジャーバージョンの更新時でもMicrosoft.Extensions.Loggingの後方互換性に高い基準を設けているため、互換性について心配する必要はありません。
ロガーの取得
ILogger インターフェイスを使用するには、まずロガーを取得する必要があります。
ロガーの取得方法は次の2つの要素に依存します。
- 構築しているアプリケーションの種類。
- ログを記録したい場所。
一般的なルールとして、以下のようになります。
- 依存性注入(DI)を使用するアプリケーション(たとえば ASP.NET Core や .NET Worker)を構築している場合、ほとんどのケースでは DI が提供するロガーを使用するべきですが、DI のロギングパイプラインが利用可能になる前や、破棄された後にログを記録したい特殊なケースもあります。 詳しくは .NET 公式ドキュメントと 5分で始める OpenTelemetry .NET ログ - ASP.NET Core アプリケーションチュートリアルを参照してください。
- DI を使用しないアプリケーションを構築している場合は、LoggerFactory インスタンスを作成し、OpenTelemetry がそれと連携するように設定してください。 詳しくは 5分で始める OpenTelemetry .NET ログ - コンソールアプリケーションチュートリアルを参照してください。
ログカテゴリ名にはドット区切りの UpperCamelCase を使用すると、ログのフィルタリングに便利です。 一般的な方法は完全修飾クラス名を使用することであり、さらに分類が必要な場合はサブカテゴリ名を追加します。 詳しくは .NET 公式ドキュメントを参照してください。 たとえば、以下のようになります。
loggerFactory.CreateLogger<MyClass>(); // これは CreateLogger("MyProduct.MyLibrary.MyClass") と同等
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass"); // 完全修飾クラス名を使用
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass.DatabaseOperations"); // サブカテゴリ名を追加
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass.FileOperations"); // 別のサブカテゴリ名を追加
ロガーの作成を頻繁に行いすぎないようにしてください。 ロガーは非常に高コストではありませんが、CPU とメモリのコストがかかるため、アプリケーション全体で再利用することを目的としています。
ログメッセージの書き方
構造化ログを使用してください。
- 構造化ログは非構造化ログよりも効率的です。
- 個々のキーバリューペアに対してフィルタリングやリダクションを行えるため、ログメッセージ全体に対して行う必要がありません。
- ストレージとインデックス作成がより効率的です。
- 構造化ログによりログの管理と利用が容易になります。
たとえば、以下のようになります。
var food = "tomato";
var price = 2.99;
logger.LogInformation("Hello from {food} {price}.", food, price);
文字列補間は避けてください。 たとえば、以下のようになります。
以下のコードは文字列補間によりパフォーマンスが低下します。
var food = "tomato";
var price = 2.99;
logger.LogInformation($"Hello from {food} {price}.");
最高のパフォーマンスを得るには、コンパイル時ログソース生成パターンを使用してください。 たとえば、以下のようになります。
var food = "tomato";
var price = 2.99;
logger.SayHello(food, price);
internal static partial class LoggerExtensions
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(this ILogger logger, string food, double price);
}
LoggerMessageAttribute を使用する場合、明示的な EventId を渡す必要はありません。
コード生成時にメソッド名のハッシュに基づいて、永続的な EventId が自動的に割り当てられます。
複雑なオブジェクトをログに記録する必要がある場合は、Microsoft.Extensions.Telemetry.Abstractions の LogPropertiesAttribute を使用してください。 詳しくは複雑なオブジェクトのログ記録チュートリアルを参照してください。
LoggerExtensions の拡張メソッドの使用は避けてください。 これらのメソッドはパフォーマンスに最適化されていません。 たとえば、以下のようになります。
以下のコードはボクシングによりパフォーマンスが低下します。
var food = "tomato";
var price = 2.99;
logger.LogInformation("Hello from {food} {price}.", food, price);
ILogger.IsEnabled の使用には高い基準を持ってください。
ロギング API は、ほとんどのロガーが特定のログレベルに対して無効になっているシナリオに高度に最適化されています。
ログ記録の前に IsEnabled を追加で呼び出しても、パフォーマンスの向上は得られません。
たとえば、以下のようになります。
以下のコードの logger.IsEnabled(LogLevel.Information) の呼び出しではパフォーマンスの向上は得られません。
var food = "tomato";
var price = 2.99;
if (logger.IsEnabled(LogLevel.Information)) // これはしないでください、パフォーマンスの向上はありません
{
logger.SayHello(food, price);
}
internal static partial class LoggerExtensions
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(this ILogger logger, string food, double price);
}
IsEnabled は、引数の評価にコストがかかる場合にパフォーマンス上の利点をもたらすことがあります。
たとえば、以下のコードではロガーが有効でない場合、Database.GetFoodPrice の呼び出しはスキップされます。
if (logger.IsEnabled(LogLevel.Information))
{
logger.SayHello(food, Database.GetFoodPrice(food));
}
上記のシナリオでは IsEnabled がパフォーマンス上の利点をもたらす場合がありますが、ほとんどのユーザーにとってはより多くの問題を引き起こす可能性があります。
たとえば、コードのパフォーマンスがどのロガーが有効かに依存するようになり、さらに引数の評価にはロギング設定に依存する重大な副作用がある可能性もあります。
コンパイル時ソースジェネレーターを使用する場合は、例外をログに記録するための専用パラメーターを使用してください。 たとえば、以下のようになります。
var food = "tomato";
var price = 2.99;
try
{
// ロジックを実行
logger.SayHello(food, price);
}
catch (Exception ex)
{
logger.SayHelloFailure(ex, food, price);
}
internal static partial class LoggerExtensions
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(this ILogger logger, string food, double price);
[LoggerMessage(Level = LogLevel.Error, Message = "Could not say hello from {food} {price}.")]
public static partial void SayHelloFailure(this ILogger logger, Exception exception, string food, double price);
}
コンパイル時ソースジェネレーターを使用する場合、検出された最初の Exception パラメーターには自動的に特別な処理が適用されます。
これはメッセージテンプレートの一部にすべきではありません。
詳しくは ログメソッドの構造 を参照してください。
ロギング拡張メソッドを使用する場合は、例外をログに記録するための専用オーバーロードを使用するべきです。
var food = "tomato";
var price = 2.99;
try
{
// ロジックを実行
logger.LogInformation("Hello from {food} {price}.", food, price);
}
catch (Exception ex)
{
logger.LogError(ex, "Could not say hello from {food} {price}.", food, price);
}
メッセージテンプレートに例外の詳細を追加することは避けてください。 たとえば、以下のようになります。
OpenTelemetry の仕様では Exception の詳細について専用の属性を定義しているため、正しい Exception API を使用する必要があります。
以下の例はやってはいけないことを示しています。
これらのケースでは詳細は失われませんが、専用の属性も追加されません。
var food = "tomato";
var price = 2.99;
try
{
// ロジックを実行
logger.SayHello(food, price);
}
catch (Exception ex)
{
logger.SayHelloFailure(food, price, ex.Message);
}
internal static partial class LoggerExtensions
{
[LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")]
public static partial void SayHello(this ILogger logger, string food, double price);
// 悪い例 - Exception はメッセージテンプレートの一部にすべきではありません。専用パラメーターを使用してください。
[LoggerMessage(Level = LogLevel.Error, Message = "Could not say hello from {food} {price} {message}.")]
public static partial void SayHelloFailure(this ILogger logger, string food, double price, string message);
}
var food = "tomato";
var price = 2.99;
try
{
// ロジックを実行
logger.LogInformation("Hello from {food} {price}.", food, price);
}
catch (Exception ex)
{
// 悪い例 - Exception はメッセージテンプレートの一部にすべきではありません。専用パラメーターを使用してください。
logger.LogError("Could not say hello from {food} {price} {message}.", food, price, ex.Message);
}
LoggerFactory
多くの場合、Microsoft.Extensions.Logging.LoggerFactory と直接やり取りせずに ILogger を使用できます。
このセクションは、LoggerFactory を明示的に作成および管理する必要があるユーザーを対象としています。
LoggerFactory インスタンスの作成を頻繁に行いすぎないようにしてください。
LoggerFactory はかなりのコストがかかり、アプリケーション全体で再利用することを目的としています。
ほとんどのアプリケーションでは、プロセスごとに1つの LoggerFactory インスタンスで十分です。
LoggerFactory インスタンスを自分で作成した場合は、そのライフサイクルを管理してください。
- アプリケーション終了前に
LoggerFactoryインスタンスの破棄を忘れると、適切なフラッシュが行われないためログが欠落する可能性があります。 LoggerFactoryインスタンスを早すぎるタイミングで破棄すると、そのロガーファクトリに関連付けられた後続のロギング API 呼び出しは no-op になる可能性があります(つまり、ログが出力されなくなります)。
ログの相関
OpenTelemetry では、ログは自動的にトレースと相関付けられます。 詳しくはログの相関チュートリアルを参照してください。
ログのフィルタリング
より高度なフィルタリングやサンプリングについては、.NET チームが .NET 9 の期間内に対応する計画があります。 進捗状況の追跡やフィードバック・提案の提供には、このランタイムイシューを使用してください。
ログのリダクション
ログにはパスワードやクレジットカード番号などの機密情報が含まれる場合があり、プライバシーとセキュリティのインシデントを防ぐために適切なリダクションが必要です。 詳しくはログのリダクションチュートリアルを参照してください。
フィードバック
このページは役に立ちましたか?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!