Using Grafana for Centralized Logging

How to set up a centralized logging solution for .Net applications with Loki and Grafana

Using Grafana for Centralized Logging

For one of my recent projects, I was looking to utilize a centralized logging solution in order to view all of my logs in a single place. I've used Azure's Application Insights before, which ties in well with App Services, but I wanted something that had the option to be self-hosted. Since I'm already using Grafana cloud, I was excited to find that Loki can be used to do this.

Sign up for Grafana Cloud

Grafana has a nice free tier, and that's what I'll be demonstrating in this post. You can sign up for an account here.

Generate an Access Token

Grafana cloud already comes with a Loki instance, but you need to generate a token in order to publish logs from a .Net application. You can do that by going to My Account and then choosing Access Policies on the left-hand side of the screen.

Then, create a new access policy with the logs:write scope and choose an appropriate name.

Once you create the token, make note of the value, as it will be used as a password later on.

In order to get the username, you can click on the Details button next to your instance in My Account

From there, you should be able to see the Details for Loki, which will contain a user in the top section.

Setup Sirilog

Next, we can install Serilog in our project, which has several Loki sinks. I installed the following:

Serilog.AspNetCore
Serilog.Sinks.Grafana.Loki

I also created an extension method to add the Serilog configuration

public class LoggingConfiguration
{
    /// <summary>
    /// The host name of the Grafana Loki instance used for capturing logs
    /// </summary>
    public string? Host { get; set; }
    
    /// <summary>
    /// The user to authenticate against Loki with
    /// </summary>
    public string? User { get; set; }
    
    /// <summary>
    /// The password to authenticate against Loki with
    /// </summary>
    public string? Password { get; set; }
    
    /// <summary>
    /// The environment to specify when writing logs
    /// </summary>
    public string? Environment { get; set; }
}

LoggingConfiguration.cs

public static IHostBuilder ConfigureKTechLogging(this IHostBuilder builder, IConfiguration configuration)
{
    var settings = new LoggingConfiguration();
    configuration.Bind(Constants.Logging.Configuration, settings);

    var sourceProject = Assembly
            .GetEntryAssembly()?.EntryPoint?.DeclaringType?.Assembly?.FullName?
        .Split(",")
        .FirstOrDefault() 
        ?? Constants.Logging.DefaultSource;
        
    builder.UseSerilog((context, config) =>
    {
        // Remove the noisy .net core logs
        config.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning);
        config.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning);
        config.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning);
            
        config.WriteTo.Console();

        if (!string.IsNullOrWhiteSpace(settings.Host))
            config.WriteTo.GrafanaLoki(
                settings.Host,
                credentials: new LokiCredentials
                {
                    Login = settings.User ?? "",
                    Password = settings.Password ?? ""
                },
                labels: [
                    new LokiLabel
                    {
                        Key = Constants.Logging.Labels.ServiceName,
                        Value = sourceProject
                    },
                    new LokiLabel
                    {
                        Key = Constants.Logging.Labels.Environment,
                        Value = settings.Environment ?? ""
                    }
                ],
                propertiesAsLabels: [
                    Constants.Logging.Labels.ServiceName,
                    Constants.Logging.Labels.Environment,
                ]
            );
    });        
       
    return builder;
}

LoggingExtensions.cs

This adds the Console and Loki sinks to Serilog, and all logs will contain the service_name and environment tags to help with filtering inside of Grafana.

Turn on Logging

Now, all that's left to do is to utilize the extension method in Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureKTechLogging(builder.Configuration);

Program.cs

This will allow us to send our logs to Loki by using the generic ILogger from Microsoft.Extensions.Logging

private readonly ILogger<WeatherForecastController> _logger;

...

_logger.LogInformation("Getting weather forecast");

View the Logs

Now, if we go to our Grafana instance and look at the connected Loki data source, we should be able to see and query against our tags