6 min read

Setup Serilog For .NET Core

Setup Serilog For .NET Core

When unexpected events occur, tracing the root cause can become incredibly difficult. Imagine an error happening, and you only hear about it minutes or even seconds later. Without proper logging, pinpointing what action triggered that error, what the subsequent response was, and the exact details of that error becomes a significant challenge.

This is where a logging utility like Serilog shines. It allows us to meticulously record application activity, essentially creating a trail of breadcrumbs. This trail helps us understand:

  • What actions were performed?
  • What responses were generated?
  • Crucially, what led to an error response.

With detailed logs, we can then examine the specifics of any error, making debugging and issue resolution much more efficient.

Getting Started with Serilog in .NET Core

To begin, we need to add the necessary Serilog libraries to our project.

Installing Serilog NuGet Packages

The primary package we'll need is Serilog.AspNetCore.

  1. Right-click on your project in the Solution Explorer.
  2. Select Manage NuGet Packages....
  3. Navigate to the Browse tab.
  4. Search for Serilog.AspNetCore.
  5. Install the package.

You might notice other Serilog packages available, such as Serilog.Sinks.File or Serilog.Sinks.Console. While we'll primarily use Serilog.AspNetCore for its integration with ASP.NET Core, you can install specific "sink" packages if you want to log to different destinations (like databases or external services) directly.

Important Note on Versions: Your installed versions might differ from mine. If prompted to update packages, proceed with caution. While updating Microsoft-related packages is usually safe, always be ready to roll back if any issues arise.

Configuring Serilog in Program.cs

Once the NuGet package is installed, we need to tell our application to use Serilog instead of the default logger. This is done within your Program.cs file.

You'll notice that the appsettings.json file might contain some basic logging configurations. However, Serilog offers a much more robust and flexible approach.

Locate the IHostBuilder configuration in your Program.cs. You'll want to add a line to integrate Serilog. Find the line that typically looks like Host.CreateDefaultBuilder(args). On a new line after this, under the builder configuration, you'll add:

.UseSerilog();

This line tells the host builder to use Serilog. You'll likely need to add a using Serilog; statement at the top of your Program.cs file to resolve this.

Now, let's move on to the actual configuration of our logger.

Initializing the Logger Configuration

We'll configure Serilog by creating a LoggerConfiguration object. This allows us to define various settings, such as where to write logs, the format of those logs, and the minimum level of messages to capture.

// In Program.cs, within the Host.CreateDefaultBuilder(args) chain, before .Build()
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information() // Set a minimum level for logging
    .WriteTo.File("Logs/app-.txt", rollingInterval: RollingInterval.Day) // Configure file logging
    .CreateLogger();

// Then, ensure your application's main execution is wrapped in a try-catch with a finally block to flush the logger.
try
{
    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.
    builder.Services.AddControllers();
    // ... other services

    // Configure the HTTP request pipeline.
    var app = builder.Build();

    // Configure the HTTP request pipeline.
    app.UseHttpsRedirection();
    app.UseAuthorization();
    app.MapControllers();

    // Log application startup
    Log.Information("Application starting up with environment: {Environment}", app.Environment.EnvironmentName);

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    Log.CloseAndFlush(); // Ensure all logs are written before exiting
}

Let's break down the key configuration aspects:

Configuring Serilog Sinks and Options

The LoggerConfiguration object allows us to chain various methods to define our logging behavior.

File Logging (WriteTo.File)

This is one of the most common ways to log application activity. It writes log messages to a specified file.

.WriteTo.File("Logs/app-.txt", rollingInterval: RollingInterval.Day)
  • "Logs/app-.txt": This is the path where your log files will be created. It's a good practice to define a specific directory for your logs, separate from your application's executable files.
    • Context is Key for Paths: When deploying your API to a server, you don't want to guess where your log files are. Centralizing them in a predictable location, perhaps on a separate drive or a dedicated logging directory, is crucial. Avoid placing them directly within your project files on the server, as this could pose a security risk and make them harder to manage for debugging purposes. You can define any path here that makes sense for your environment.
    • The Mysterious Dash: You might notice the dash (-) in app-.txt. This is related to the rollingInterval we'll discuss next.
  • rollingInterval: RollingInterval.Day: This is a powerful feature that dictates how log files are managed. RollingInterval.Day means a new log file will be created each day. This is incredibly useful for segmenting your logs. If you need to investigate an issue that occurred last Thursday, you can easily find Thursday's log file instead of sifting through one massive file that contains months or years of data. Other options include RollingInterval.HourRollingInterval.MonthRollingInterval.Year, and RollingInterval.Infinite.

Output Template (outputTemplate)

How do you want each log entry to look? The outputTemplate defines the format of each line in your log file.

.WriteTo.File(
    "Logs/app-.txt",
    rollingInterval: RollingInterval.Day,
    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff z} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
)

Let's dissect this template:

  • {Timestamp:yyyy-MM-dd HH:mm:ss.fff z}: This includes a detailed timestamp, showing the year, month, day, hour, minute, second, milliseconds, and the timezone offset. You can customize this format or remove parts you don't need.
  • [{Level:u3}]: This displays the log level (e.g., Information, Warning, Error) in a concise format (e.g., INFWRN).
  • {Message:lj}: This is the actual log message. The :lj part ensures literal JSON logging if the message itself is JSON.
  • {NewLine}: Inserts a newline character.
  • {Exception}: If an exception is logged, its details will be appended here.

Restricted Minimum Level (MinimumLevel)

You don't always need to log every single minor event, especially during development or in production. The MinimumLevel setting allows you to filter out less critical messages.

.MinimumLevel.Information()

This line specifies that only log messages with a level of Information or higher (e.g., WarningErrorFatal) will be recorded. Other common levels include DebugVerboseWarningError, and Fatal. You can adjust this based on your needs. For instance, during development, you might set it to Debug to capture more granular details.

Creating the Logger

Finally, after defining all your configurations, you need to create the logger instance:

.CreateLogger();

This method finalizes the configuration and returns the ILogger object that you'll use throughout your application.

Using the Logger in Your Application

Now that Serilog is configured, we can start using it to log events. It's good practice to wrap your application's main execution within a try-catch-finally block.

finally block: This block will execute regardless of whether an exception occurred or not. It's the perfect place to ensure that all buffered log messages are written to their destinations and the logger is properly closed.

finally
{
    Log.CloseAndFlush();
}

This is critical for preventing data loss.

catch block: If any unhandled exceptions occur during the execution of your application, this block will catch them. This is where you'd log a Fatal error.

catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}

Logging the exception object (ex) along with a descriptive message provides all the necessary details for debugging.

try block: This is where your application's core logic will run. You can log informational messages here, such as when the application starts.

Log.Information("Application starting up with environment: {Environment}", app.Environment.EnvironmentName);

Notice how we use named parameters ({Environment}) for structured logging, which can be very useful for querying logs later.

Testing Your Serilog Setup

After implementing the configuration, run your application. You should find a Logs folder created in your project's root directory (or wherever you specified). Inside this folder, you'll see a daily log file (e.g., app-20231027.txt). Opening this file will reveal the log messages, formatted according to your outputTemplate, including the startup message.

If your API starts up successfully and you see the "Application starting up" message in your log file, congratulations! You've successfully set up Serilog for file logging.

What's Next?

This setup provides a solid foundation for logging in your .NET Core applications. From here, you can explore more advanced Serilog features:

  • Logging to the Console: Add .WriteTo.Console() to your LoggerConfiguration to see logs in your application's output window simultaneously.
  • Structured Logging: Utilize named parameters in your log messages for better querying and analysis.
  • Custom Sinks: Integrate with databases, cloud logging services (like Seq, ELK stack), or other destinations.
  • Enrichers: Add contextual information (like user ID, request ID) to all log events automatically.

By mastering Serilog, you gain invaluable insight into your application's behavior, making development, debugging, and maintenance significantly more manageable.