[C#] Implementing Ultra-Fast Zero-Allocation Logging with ZLogger

ZLogger is a library that avoids memory allocation caused by C# string interpolation ($””), enabling extremely high-speed log output. It is highly effective for systems that handle large volumes of logs, such as IoT systems or game servers where performance is critical.

This article explains how to implement logging to the console and files (with rotation) using version 1.4.1.

目次

Implementation Sample: IoT Sensor Data Collection

We will create a “Sensor Collection Service (SensorApp)” that continuously receives large amounts of sensor data.

1. Package Installation

Run the following command to install the package:

dotnet add package ZLogger --version 1.4.1

2. Program.cs (ZLogger Configuration)

We will use ZString.PrepareUtf8 to speed up the log prefix (timestamp and level) and set the output to the console and files.

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; // Required for ZString

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

        // Common log options configuration
        static readonly Action<ZLoggerOptions> LogOption = options =>
        {
            // Define the prefix format at the start of the log
            // Formats as "[LogLevel][yyyy-MM-dd HH:mm:ss] " and pre-compiles it for speed
            var prefixFormat = ZString.PrepareUtf8<LogLevel, DateTime>("[{0}][{1}] ");

            options.PrefixFormatter = (writer, info) =>
                prefixFormat.FormatTo(ref writer, info.LogLevel, info.Timestamp.DateTime.ToLocalTime());
            
            // Enable structured logging for stack traces during exceptions
            options.EnableStructuredLogging = true;
        };

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging((context, logging) =>
                {
                    // Clear standard providers
                    logging.ClearProviders();
                    
                    // Set minimum log level to Trace
                    logging.SetMinimumLevel(LogLevel.Trace);

                    // 1. Console Output (High-speed version)
                    logging.AddZLoggerConsole(LogOption);

                    // 2. Single File Output
                    logging.AddZLoggerFile("logs/sensor.log", LogOption);

                    // 3. Rolling File Output
                    // Changes file by date and splits if the file exceeds 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 (Output via ZLogger-Specific Methods)

To maximize ZLogger’s performance, use the extension methods like ZLogInformation instead of the standard LogInformation. Using the generic version helps avoid boxing.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ZLogger; // Required for ZLog methods

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

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

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // Dummy data for log output
            var sensorId = "Sensor-001";
            var temperature = 24.5;
            var humidity = 60;

            // --- ZLogger Usage ---
            
            // 1. Trace: Detailed log
            _logger.ZLogTrace("Detail Trace: Waiting for data reception...");

            // 2. Debug: Debug information
            // Writing as ZLogDebug<T1, T2>(format, arg1, arg2) 
            // avoids boxing (memory allocation) and outputs with zero allocation.
            _logger.ZLogDebug("Debug: Connection check OK (Target: {0})", "Gateway-A");

            // 3. Information: Standard log
            _logger.ZLogInformation("Received: SensorId={0}, Temp={1}C, Hum={2}%", sensorId, temperature, humidity);

            // 4. Warning: Warning
            _logger.ZLogWarning("Warning: Humidity exceeds threshold (Current: {0}%)", humidity);

            // 5. Error: Error
            // You can pass the exception object as the first argument
            try
            {
                throw new InvalidOperationException("Communication timeout");
            }
            catch (Exception ex)
            {
                _logger.ZLogError(ex, "Error: Data transmission failed (Retry: {0})", 1);
            }

            // 6. Critical: Critical error
            _logger.ZLogCritical("Critical: Emergency system shutdown");

            return Task.CompletedTask;
        }
    }
}

Execution Results (Example Log Output)

The output in logs/sensor-2026-01-11_000.log will look like this:

[Trace][2026-01-11 10:00:00] Detail Trace: Waiting for data reception...
[Debug][2026-01-11 10:00:00] Debug: Connection check OK (Target: Gateway-A)
[Information][2026-01-11 10:00:00] Received: SensorId=Sensor-001, Temp=24.5C, Hum=60%
...

Explanation and Key Points

  • Using ZLog Methods: If you write _logger.LogInformation($"Val={val}"), memory is allocated to create the string. By writing _logger.ZLogInformation("Val={0}", val), ZLogger writes directly to the buffer. This reduces Garbage Collection (GC) and achieves overwhelming speed.
  • Speeding up PrefixFormatter: Using ZString.PrepareUtf8 allows you to pre-analyze and store the log format string. This saves the cost of analyzing the format every time, which is very effective when log frequency is high.
  • Note on Versions (v1 vs v2): This guide uses version 1.4.1. Currently, v2 is released with major changes to the API, namespaces, and configuration (such as how to write AddZLoggerConsole). If you are starting a new project without version constraints, consider using v2.
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次