Tedd.ZeroAllocationLogger 1.0.1

dotnet add package Tedd.ZeroAllocationLogger --version 1.0.1
                    
NuGet\Install-Package Tedd.ZeroAllocationLogger -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tedd.ZeroAllocationLogger" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tedd.ZeroAllocationLogger" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Tedd.ZeroAllocationLogger" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tedd.ZeroAllocationLogger --version 1.0.1
                    
#r "nuget: Tedd.ZeroAllocationLogger, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=Tedd.ZeroAllocationLogger&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Tedd.ZeroAllocationLogger&version=1.0.1
                    
Install as a Cake Tool

Tedd.ZeroAllocationLogger

Zero allocating file logger

Uses native memory mapping of log file and various techniques do a fully allocation free buffered write to log file.

Use case

This library is for very special use cases where you want to completely avoid heap allocations. Normally writing logs involves a lot of temporary string allocations. Though these are short lived and quickly cleaned up by the GC, they do add overhead to your application. This library is severely limited, only providing the most basic of logging functionality, but does not allocate anything and minimizes memory copying.

How it works

Use the operating system memory paging system to map the log file into the applications memory area. This creates a virtual 1-1 mapping between the applications memory and the file on disk.

Write-commands for data types uses various techniques to write directly to this memory area. For instance an Int32 will be written as its string representation "-12345", not its underlying bytes. Since the string representation is variable length, a stack allocated buffer is used for temporary write, then the bytes are copied to memory area.

This means you have to make one write call per datatype. If you are doing multithreading you should fetch a lock object to ensure consistency in the writes.

Buffered write

You can't tail this log file.

Why? Once file is opened, its size is immediately extended by 128MB (sparse records). If log has >65MB appended it will extend the file size with another 65MB. Once file is closed, whatever slack at the end of the file remains will be released. Since tail looks at file size, not content, it will not be able to follow the logs.

File is flushed when 6.4MB of data has been written to it, and when it is closed.

Examples

Opening and closing file

Log.Open("Log.txt");
Log.WriteDateTimeStamp();
Log.WriteLine(" Hello"u8);
Log.Close();

Note that the file is opened as shared, other applications can read from it while it is being used. But you can't open it for shared from within the same process as you are writing with.

Strings

Strings may cause allocation (if they are more than 1024 bytes), so they are tagged as experimental. They also have to be converted from Unicode to bytes, causing extra processing and extra copy operations.

You need to explicitly disable the check for writing a string.

#pragma warning disable LOG_ALLOCATES
Log.WriteLine("Test");
#pragma warning restore LOG_ALLOCATES

Instead consider using u8 datatype which avoids both allocation and Unicode conversion:

Log.WriteLine("Test"u8);

Thread safety

If you are writing to log with multiple threads you may want to lock the log. This will block other threads while the writing happens, so keep it short.

using (Log.AcquireScopedLock())
{
    Log.WriteDateTimeStamp(DateTime.UtcNow);
	Log.Write("Client "u8);
	Log.Write(socket.RemoteEndPoint);
	Log.WriteLine(" connected.");
}

Benchmarks

Using this logger is a tradeoff between features and speed. Serilog is very fast, and I do recommend using it over this library.

That being said, this library clocks in at 3-5 times faster than Serilog through the Microsoft ILogger interface (buffered write).


BenchmarkDotNet v0.15.1, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley)
Unknown processor
.NET SDK 9.0.300
  [Host]         : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2 [AttachedDebugger]
  ShortRunConfig : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2

Job=ShortRunConfig  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method numTasks Mean Error StdDev Ratio RatioSD Rank Gen0 Gen1 Allocated Alloc Ratio
ZeroAllocLogger_ComplexMessage ? 289.36 ns 696.48 ns 38.176 ns 0.33 0.04 1 - - - 0.00
NetLogger_ComplexMessage ? 876.48 ns 611.17 ns 33.500 ns 1.00 0.05 2 0.0572 - 968 B 1.00
ZeroAllocLogger_LogInt ? 207.99 ns 2,351.47 ns 128.892 ns 0.55 0.30 1 - - - 0.00
NetLogger_LogInt ? 378.14 ns 20.50 ns 1.124 ns 1.00 0.00 2 0.0319 - 536 B 1.00
Multithreaded_ZeroAllocLogger 100000 12,887,498.96 ns 32,417,771.33 ns 1,776,927.443 ns 0.33 0.04 1 - - 11952 B 0.000
Multithreaded_NetLogger 100000 39,373,289.74 ns 5,718,814.10 ns 313,467.499 ns 1.00 0.01 2 3230.7692 230.7692 53612634 B 1.000
Multithreaded_ZeroAllocLogger 1000000 92,837,614.29 ns 100,821,689.42 ns 5,526,377.027 ns 0.24 0.01 1 - - 11955 B 0.000
Multithreaded_NetLogger 1000000 392,107,766.67 ns 19,836,275.10 ns 1,087,293.177 ns 1.00 0.00 2 32000.0000 1000.0000 536011176 B 1.000
ZeroAllocLogger_LogString ? 98.53 ns 390.85 ns 21.424 ns 0.29 0.06 1 - - - 0.00
NetLogger_LogString ? 334.41 ns 11.81 ns 0.647 ns 1.00 0.00 2 0.0272 - 456 B 1.00
ZeroAllocLogger_LogUtf8Bytes ? 78.52 ns 340.52 ns 18.665 ns 0.21 0.04 1 - - - 0.00
NetLogger_LogUtf8Bytes ? 373.72 ns 12.40 ns 0.680 ns 1.00 0.00 2 0.0319 - 536 B 1.00
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 272 6/10/2025
1.0.0 274 6/10/2025

Allocation size adjustment