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.PrepareUtf8allows 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.
