【C#】ZLoggerを使って超高速・ゼロアロケーションなログ出力を実装する

ZLogger は、C# の文字列補間 ($"") によるメモリ確保(アロケーション)を回避し、非常に高速にログを出力できるライブラリです。大量のログを扱うシステムや、パフォーマンスが要求されるIoT/ゲームサーバーなどで威力を発揮します。

ここでは、指定されたバージョン 1.4.1 を前提に、コンソールとファイル(ローテーション付き)への出力を実装します。

目次

実装サンプル:IoTセンサーデータの収集

題材として、大量のセンサーデータを受け取り続ける「センサー収集サービス (SensorApp)」を作成します。

1. パッケージのインストール

dotnet add package ZLogger --version 1.4.1

2. Program.cs (ZLoggerの設定)

ZLogger の特徴である ZString.PrepareUtf8 を使ったプレフィックス(ログの先頭につく日時やレベル)の高速化設定を行い、コンソールとファイルに出力先を設定します。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ZLogger;
using Cysharp.Text; // ZStringを使用するために必要

namespace SensorApp
{
    class Program
    {
        static async Task Main(string[] args) =>
            await CreateHostBuilder(args).Build().RunAsync();

        // ログの共通オプション設定
        static readonly Action<ZLoggerOptions> LogOption = options =>
        {
            // ログの先頭に付くプレフィックスのフォーマット定義
            // "[LogLevel][yyyy-MM-dd HH:mm:ss] " の形式を事前コンパイルして高速化
            var prefixFormat = ZString.PrepareUtf8<LogLevel, DateTime>("[{0}][{1}] ");

            options.PrefixFormatter = (writer, info) =>
                prefixFormat.FormatTo(ref writer, info.LogLevel, info.Timestamp.DateTime.ToLocalTime());
            
            // 例外発生時のスタックトレース出力も有効化
            options.EnableStructuredLogging = true;
        };

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging((context, logging) =>
                {
                    // 標準のプロバイダをクリア
                    logging.ClearProviders();
                    
                    // 最低ログレベルをTraceに設定
                    logging.SetMinimumLevel(LogLevel.Trace);

                    // 1. コンソール出力 (高速版)
                    logging.AddZLoggerConsole(LogOption);

                    // 2. 単一ファイル出力
                    logging.AddZLoggerFile("logs/sensor.log", LogOption);

                    // 3. ローテーション付きファイル出力
                    // 日付ごとにファイルを変え、1ファイルあたり2MB(2048KB)を超えたら分割
                    logging.AddZLoggerRollingFile(
                        fileNameSelector: (dt, seq) => $"logs/sensor-{dt.ToLocalTime():yyyy-MM-dd}_{seq:000}.log",
                        timestampPattern: x => x.ToLocalTime().Date,
                        rollSizeKB: 2048,
                        options: LogOption);
                })
                .ConfigureServices((context, services) =>
                {
                    services.AddHostedService<SensorWorker>();
                });
    }
}

3. SensorWorker.cs (ZLogger独自メソッドによる出力)

ZLogger の性能を最大限に引き出すには、標準の LogInformation ではなく、拡張メソッドである ZLogInformation などを使い、ジェネリクス版の書き方をするのがポイントです。

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ZLogger; // これがないとZLog系メソッドが使えません

namespace SensorApp
{
    public class SensorWorker : BackgroundService
    {
        private readonly ILogger<SensorWorker> _logger;

        public SensorWorker(ILogger<SensorWorker> logger)
        {
            _logger = logger;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // ログ出力用のダミーデータ
            var sensorId = "Sensor-001";
            var temperature = 24.5;
            var humidity = 60;

            // --- ZLogger の書き方 ---
            
            // 1. Trace: 詳細ログ
            _logger.ZLogTrace("詳細トレース: データ受信待機中...");

            // 2. Debug: デバッグ情報
            // ZLogDebug<T1, T2>(format, arg1, arg2) のように書くことで
            // Boxing(メモリ確保)を回避してゼロアロケーションで出力できます。
            _logger.ZLogDebug("デバッグ: 接続チェック OK (Target: {0})", "Gateway-A");

            // 3. Information: 通常ログ
            _logger.ZLogInformation("受信: SensorId={0}, Temp={1}C, Hum={2}%", sensorId, temperature, humidity);

            // 4. Warning: 警告
            _logger.ZLogWarning("警告: 湿度が閾値を超えています (Current: {0}%)", humidity);

            // 5. Error: エラー
            // 例外オブジェクトも最初の引数に渡せます
            try
            {
                throw new InvalidOperationException("通信タイムアウト");
            }
            catch (Exception ex)
            {
                _logger.ZLogError(ex, "エラー: データの送信に失敗しました (Retry: {0})", 1);
            }

            // 6. Critical: 致命的エラー
            _logger.ZLogCritical("致命的: システムを緊急停止します");

            return Task.CompletedTask;
        }
    }
}

実行結果(ログファイルの出力例)

logs/sensor-2026-01-11_000.log に以下のような形式で出力されます。

[Trace][2026-01-11 10:00:00] 詳細トレース: データ受信待機中...
[Debug][2026-01-11 10:00:00] デバッグ: 接続チェック OK (Target: Gateway-A)
[Information][2026-01-11 10:00:00] 受信: SensorId=Sensor-001, Temp=24.5C, Hum=60%
...

解説とポイント

  1. ZLog メソッドの利用*: _logger.LogInformation($"Val={val}") と書くと、文字列生成のためにメモリが確保されてしまいます。 _logger.ZLogInformation("Val={0}", val) と書くことで、ZLogger が内部でバッファに直接書き込みを行うため、GC(ガベージコレクション)の発生を抑え、圧倒的な速度を実現します。
  2. PrefixFormatter の高速化: ZString.PrepareUtf8 を使うことで、ログのフォーマット文字列を事前に解析・保持しておけます。毎回フォーマット解析をするコストが省けるため、ログ出力頻度が高い場合に非常に有効なテクニックです。
  3. 注意点 (v1 vs v2): 今回は指定のあった v1.4.1 で実装しましたが、現在は v2 がリリースされており、APIや名前空間、設定方法が大きく変更されています(AddZLoggerConsole の書き方など)。新規プロジェクトでバージョン指定がない場合は v2 の利用を検討してください。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次