I’ve just released a new logging library ZLogger for .NET Core and Unity. It is very fast, and using previously introduced ZString — Zero Allocation StringBuilder in the backend, it eliminates the allocation when formatting the string.
The standard output is especially important for containerization. For example, Datadog Logs and Stackdriver Logging collect logs directly from the standard output of the container. Writing to standard output is also recommended in The Twelve-Factor App — XI. Logs section. As such, dealing with over-decorated logs for the local environment and standard output in a slow Console.WriteLine is too old-fashioned an idea, and unfortunately, until now, no library has taken that into account in C#.
There is a lot of waste in the old logging process.
The standard String.Format causes boxing to take all the values as objects, and also creates a new UTF16 String. Encode this String in UTF8, will be the flow to write to the final stream, but for ZLogger ZString format the string directly into the buffer area using ZString as UTF8 write, and streamed together in the ConsoleStream. There is no primitive boxing and it is written asynchronously and at once, so there is no load on the caller or the application as a whole.
I hope that ZLogger will make you think again about the performance of loggers.
The logger setup follows ConfigureLogging with Generic Host. Therefore, you can also set up filtering, etc., using Microsoft.Extensions.Logging.
Basically, the logger is injected by DI (Please refer to [ReadMe#Global LoggerFactory if you want to use it like LogManager.GetLogger). The basic flow is to use ZLogDebug and ZLogInformation with Z at the beginning instead of LogDebug and LogInformation.
The standard Log method has a method definition of (string format, object args), so you can’t avoid the boxing. For this reason, I have defined a large number of overloads that we have prepared in our own generics.
There is a very high chance of mistakes (i.e., accidentally using the Log method when you intend to use the ZLog method) due to the mixture of the standard Log method and the ZLog method, which is described in Microsoft.CodeAnalysis.BannedApiAnalyzers to treat the standard Log method as a warning/error in your code.
By configuring these BannedApiAnalyzers, you can ensure that ZLogger is used at high performance.
Log management cloud services, such as Datadog Logs and Stackdriver Logging, allow flexible filtering, searching, and querying with expressions, but logs must be properly parsed in order to take full advantage of their capabilities. ZLogger can switch between text message output and JSON output by enabling EnableStructuredLogging. In addition, it is possible to switch between multiple log providers, so it is possible to set up text messages for console output and JSON for file output.
Also in the StructuredLogging, write directly to UTF8 binary by System.Text.Json’s Utf8JsonWriter, and transparent passing of buffers using IBufferWriter, so thoroughly prevented any allocations related to JSON processing and performance degradation due to the generation of superfluous copies.
Implements Microsoft.Extensions.Logging directly
Since most .NET Core applications are currently built on top of .NET Generic Host (even console applications! For example, ConsoleAppFramework, which is developing Cysharp(my company), can easily run CLI applications on .NET Generic Hosts), read the configuration, DI and logger by Microsoft.Extensions framework.
Many loggers have separate frameworks and integration in the form of a bridge to “Microsoft.Extensions.Logging”. This means that the log output will go through two frameworks, lengthening the pipeline and leading to performance degradation.
“Microsoft.Extensions.Logging” is a good enough framework for log levels, filtering and registration of multiple destinations. Therefore, we minimize the overhead by not creating our own logging framework and implementing it as directly on top of “Microsoft.Extensions.Logging” as possible.
“Microsoft.Extensions.Logging” output providers are currently only providing minimal or Azure-dependent output, so it was difficult to make it practical on its own. Apart from performance, ZLogger has enough providers such as Console, File, RollingFile, and Stream to make “Microsoft.Extensions.Logging” alone practical.
Also, since all initialization/termination processes are integrated into Generic Host, the logger configuration is very simple.
It is not that Unity has only Debug.Log (inside is Debug.unityLogger -> DebugLogHandler -> Internal_Log), but it is very poor, the implementation is only DebugLogHandler, and Debug.unityLogger is irreplaceable, so it almost doesn’t function as a logging framework.
Using ZLogger in Unity offers advantages over the standard, such as “multiple log providers including file output,” “standard log levels and filtering,” and “categorization per logger. File output is not very useful for mobile applications, but it would be nice if it existed in PC applications such as VR.
Also, categorization with loggers is very useful for filtering in log consoles that are more powerful than the norm, such as EditorConsolePro (e.g. categorization of [UI], [Battle], [Network], etc.).
ZLogger is designed to deliver maximum performance under standard conditions with no extra settings. Give this new logging library a try, redesigned from a modern perspective.