C#
logging
programming
.NET
best practices

How do I do logging in C without using 3rd party libraries?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

You can do perfectly serviceable logging in C# without bringing in third-party packages, especially for small applications, tools, or internal services. The main design question is not whether logging is possible, but how structured, persistent, and thread-safe you need it to be.

Start With the Smallest Useful Logger

At minimum, a logger needs:

  • a timestamp
  • a severity level
  • a message
  • a destination such as console or file

A simple console logger is often enough during development.

csharp
1using System;
2
3public static class Log
4{
5    public static void Info(string message) => Write("INFO", message);
6    public static void Error(string message) => Write("ERROR", message);
7
8    private static void Write(string level, string message)
9    {
10        Console.WriteLine($"[{DateTime.UtcNow:O}] [{level}] {message}");
11    }
12}
13
14Log.Info("Application started");
15Log.Error("Something failed");

This is not fancy, but it is already useful and has zero external dependencies.

File Logging With Built-In APIs

If logs need to survive process exit, write them to a file.

csharp
1using System;
2using System.IO;
3
4public static class FileLog
5{
6    private static readonly string PathName = "app.log";
7    private static readonly object Sync = new object();
8
9    public static void Write(string level, string message)
10    {
11        var line = $"[{DateTime.UtcNow:O}] [{level}] {message}{Environment.NewLine}";
12        lock (Sync)
13        {
14            File.AppendAllText(PathName, line);
15        }
16    }
17}
18
19FileLog.Write("INFO", "File logging ready");

The lock matters because concurrent writes from multiple threads can otherwise interleave or corrupt log lines.

Add Log Levels Explicitly

As a system grows, log levels become important because not every message deserves the same treatment.

csharp
1public enum LogLevel
2{
3    Debug,
4    Info,
5    Warning,
6    Error
7}

Then gate writes based on a configured minimum level. That prevents debug noise from overwhelming production output.

Logging Exceptions Cleanly

A logger is most useful when it preserves stack traces and context.

csharp
1try
2{
3    throw new InvalidOperationException("bad state");
4}
5catch (Exception ex)
6{
7    FileLog.Write("ERROR", $"Unhandled exception: {ex}");
8}

Using ex.ToString() or string interpolation with ex is usually better than logging only ex.Message, because you keep the stack trace and nested exception details.

Windows Event Log Is Another Built-In Option

On Windows-only systems, the Event Log is a native destination.

csharp
1using System.Diagnostics;
2
3if (!EventLog.SourceExists("MyApp"))
4{
5    EventLog.CreateEventSource("MyApp", "Application");
6}
7
8EventLog.WriteEntry("MyApp", "Application started", EventLogEntryType.Information);

This integrates well with Windows administration, but it is platform-specific and may require elevated permissions to create a new event source.

Keep the Format Predictable

Even a homemade logger benefits from consistency. A predictable line format makes grep, scripts, and log review much easier.

Useful fields include:

  • timestamp in UTC
  • level
  • subsystem or category
  • message
  • optional correlation or request ID

If you ever need to grow into structured logging later, having disciplined fields from the start makes migration easier.

What You Give Up Without a Library

Third-party logging frameworks usually provide:

  • sinks for many destinations
  • asynchronous buffering
  • rolling file management
  • structured JSON output
  • filtering by namespace or source

Without them, you write more plumbing yourself. That is acceptable for smaller systems, but for larger or long-lived production services, built-in-only logging can become limiting.

Common Pitfalls

The biggest mistake is writing directly to a file from many threads without synchronization.

Another mistake is logging only messages and dropping exception stack traces.

A third issue is using local time without timezone context. UTC timestamps are usually much easier to work with across machines and logs.

Finally, if you keep appending forever to one file, log size becomes an operational problem. Even simple in-house logging benefits from rotation or truncation strategy.

Summary

  • C# can do useful logging with only built-in APIs such as Console, File, and EventLog.
  • Start with a simple logger that includes timestamp, level, and message.
  • Use locking or another synchronization approach for file logging.
  • Log full exceptions, not just exception messages.
  • Prefer UTC timestamps for consistency.
  • Third-party libraries add convenience, but basic reliable logging does not require them.

Course illustration
Course illustration

All Rights Reserved.