Back to Archive

Reusable .NET tooling

EzLogger

EzLogger is the logger I ended up writing after getting tired of dragging heavier logging frameworks into small .NET tools just to get colored terminal output, timestamps, verbosity levels, and optional file logs. The first rough version of this idea showed up back in the 2022 Energy Logger project. When I came back to desktop and CLI work later, I rebuilt it properly as a reusable library.

EzLogger preview image

The newer version kept the part that mattered and dropped the rest: six verbosity levels, colored console output, file logging, background writes, automatic cleanup, and graceful shutdown flushing. Since then it has gone into basically every C# app I write, both for real work and for experiments.

Project Brief

Platform
.NET Standard 2.0+ / .NET 6+
Type
Reusable logging library for CLI and desktop tools
Origin
Started as a rough logger in 2022, rebuilt as a reusable library in 2024
Use
Shared across my .NET tooling work, from production utilities to small experiments
Distribution
NuGet package and direct project reuse
6severity levels
2output targets
3y+used in my own C# tools

1.Project Overview

The main issue was simple: most logging libraries felt heavier than the tools I was writing. I did not want to spend time wiring up a framework with extra dependencies just to print colored messages to the terminal, attach timestamps, set a verbosity level, and maybe write the same output to a file.

The first version of this logger was much rougher and lived inside the Energy Logger project in 2022. Later, when I started writing desktop and CLI applications again, I rebuilt the idea as a proper reusable library. That second version is EzLogger.

It was built partly for fun and partly because I genuinely needed it. Since then it has become one of those tools I stop thinking about. It just goes into every C# app I write and gets out of the way.

2.From Simple Writes to Background Logging

The early version was synchronous. It just wrote to console and file directly, which was fine at first because the goal was simply to have a logger that was easy to drop into a small app. But once I wanted to use it in multi-threaded tools, that simple version stopped being good enough. I wanted logging calls to return quickly, and I did not want to worry about threads stepping on each other.

So the library moved to a queued background model. When the application emits a log message, the message is timestamped, converted into an internal log record, and pushed into the logging pipeline. Console and file writes then happen in background services instead of on the caller thread. Multiline messages are split into individual lines while preserving the original timestamp, which sounds like a small detail, but it keeps the output much easier to read when things get messy.

That change solved the part that mattered most in practice: logging became much faster from the caller side, and I no longer had to feel nervous about using it in a multi-threaded application.

  • Non-blocking writes: the caller returns quickly while console and file work happen in the background.
  • Consistent line timestamps: every line of a multiline message carries the same original timestamp.
  • Safer multi-threaded use: background processing removed most of the worry about concurrent log writes.
EzLogger internal process flow diagram showing the queue-based pipeline from log emission through batch processing to console and file output targets.

Figure 1: EzLogger Internal Process Flow

3.What Was Actually Tricky

Concurrency and Speed

These were the real reasons the simple first version had to change. I needed the logger to behave properly in multi-threaded applications, and I also wanted it to be fast enough that I would not hesitate to leave it enabled in normal tooling work. That pushed the design toward background processing, batching, and some general low-level cleanup instead of direct writes everywhere.

Shutdown Flushing

This part was a bit annoying in the way shutdown code usually is. The library had to stop accepting new work, drain the queues, wait for the background tasks to finish, and still avoid dropping the last few messages if the application was already on its way out. That was one of the messier parts of making it feel reliable.

Retention Without Babysitting

File logging was useful, but only if it did not quietly fill the disk over time. So the library got a separate cleaner service that watches the total log folder size and deletes the oldest files once the configured cap is exceeded. It is not glamorous, but it is exactly the kind of thing I wanted the logger to handle by itself.

Fast Enough, Not a Logging Science Project

I did benchmark it and test it enough to be comfortable using it, but I am not pretending this is the answer for every high-volume logging problem. Quite frankly, that was never the goal. The goal was a logger that is fast enough for the CLI and desktop tools I actually write, and on that front it has done the job just fine.

4.Design Choices and Limits

Feature Set Chosen on Purpose

The feature set came almost entirely from personal requirements. I wanted six verbosity levels, console output, optional file output, automatic cleanup, and a configuration model that stays simple. Anything beyond that had to justify its existence. Most things did not.

Why "Announce" Exists

Announce is there for the two or three events in an app that actually deserve to stand out: startup, shutdown, or some major milestone completing. It is meant to be used rarely. That is why it exists as its own level instead of just being another Info message in the crowd.

What I Deliberately Left Out

I did not want the library to turn into a logging framework hobby horse with every fancy feature under the sun. The whole point was to keep something that works for me and stays out of my way. That tradeoff is visible throughout the design: fewer knobs, fewer dependencies, and much less ceremony.

Conclusion

EzLogger is not the most configurable logger in the .NET world, and that is fine. I built it because I was tired of solving the same small logging problem over and over, and because the heavier options kept feeling like too much machinery for the kind of tools I write.

What I like most about it now is not any one internal detail. It is the fact that it has become boring in the best way. I drop it into a C# tool, set the verbosity, maybe enable file logging, and move on to the actual work. For a utility library, that is about as good an outcome as you can ask for.