Minimal APIs: Serilog

By | 07/02/2024

In this post, we will see how to use Serilog in our Minimal APIs projects.
Serilog is a diagnostic logging library fro .NET applications and we have seen it in the post:
C# – Serilog.
The Minimal API project that we are going to use here, is the project that we have used in all previous Minimal APIs’ posts and, the last and more complete is: Minimal APIs – Autorization.

First of all, via the NuGet Packet Manager, we are going to install some Serilog packages:

Install-Package Serilog.AspNetCore
Install-Package Serilog.Settings.Configuration
Install-Package Serilog.Sinks.Console
Install-Package Serilog.Sinks.File


Then, we modify the ‘appsettings.json’ file in our project, including some Serilog settings:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Error"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": { "path": "ApiLogs.txt", "rollingInterval": "Day" }
      }
    ],
    "Enrich": [ "FromLogContext" ],
    "Properties": {
      "Application": "YourApplicationName"
    }
  },
  "AllowedHosts": "*"
}


Finally, we will add the Serilog’s configuration in Program.cs:

// Configure Serilog
var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .CreateLogger();

Log.Logger = logger;

builder.Host.UseSerilog();


Now, we will modify all methods in order to add Serilog:
GET ALL DOGS

app.MapGet("/dog", async (IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the beginning of the request to get all dogs
    loggerInput.LogInformation("Requesting all dogs");

    // Execute the GetAllDogs command to retrieve all dogs
    var dogs = await commands.GetAllDogs();

    // Check if the result is null or empty
    if (dogs == null || !dogs.Any())
    {
        // Log a warning indicating that no dogs were found
        loggerInput.LogWarning("No dogs found");

        // Return a NotFound result to indicate that no dogs were found
        return Results.NotFound();
    }

    // Log the successful retrieval of dogs, including the count
    loggerInput.LogInformation($"Retrieved {dogs.Count} dogs");

    // Return an Ok result with the list of retrieved dogs
    return Results.Ok(dogs);
}).RequireAuthorization();

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 15:38:29.771 +01:00 [INF] Requesting all dogs
2024-02-04 15:38:29.775 +01:00 [INF] BLL - Retrieving all dogs
2024-02-04 15:38:29.973 +01:00 [INF] Retrieved 1 dogs



ADD DOG

// Definition Post Method
app.MapPost("/dog", async (Dog dog, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    try
    {
        // Log the attempt to add a new dog with the dog's details
        loggerInput.LogInformation("Attempting to add a new dog: {@Dog}", dog);

        // Attempt to add the dog using the command pattern implementation
        var addSuccess = await commands.AddDog(dog);

        // Check if the addition was successful
        if (!addSuccess)
        {
            // Log a warning if adding the dog failed and return a problem result
            loggerInput.LogWarning("Failed to add a new dog: {@Dog}", dog);
            return Results.Problem("Failed to add a new dog.");
        }

        // Save changes to the database
        await commands.Save();

        // Log the successful addition of the dog
        loggerInput.LogInformation("New dog added successfully: {@Dog}", dog);

        // Return an OK result upon successful addition
        return Results.Ok();
    }
    catch (Exception ex)
    {
        // Log any exceptions that occur during the process
        loggerInput.LogError(ex, "Error occurred while adding a new dog: {@Dog}", dog);

        // Return a problem result in case of exceptions
        return Results.Problem("An error occurred while processing your request.");
    }
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 14:07:39.875 +01:00 [INF] Requesting all dogs
2024-02-04 14:07:40.488 +01:00 [WRN] No dogs found
2024-02-04 14:07:51.546 +01:00 [INF] Attempting to add a new dog: {"Id":0,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:07:51.733 +01:00 [INF] New dog added successfully: {"Id":1,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:08:08.776 +01:00 [INF] Requesting all dogs
2024-02-04 14:08:08.801 +01:00 [INF] Retrieved 1 dogs



GET DOG BY ID

// Definition Get{Id} Method
app.MapGet("/dog/{id}", async (int id, IDogCommands commands,  ILogger<Program> loggerInput) =>
{
    // Log the attempt to retrieve a dog with a specific ID
    loggerInput.LogInformation("Attempting to retrieve dog with ID: {DogId}", id);

    // Execute the GetDogById command to retrieve the dog with the specified ID
    var dog = await commands.GetDogById(id);

    // Check if a dog with the specified ID was found
    if (dog == null)
    {
        // Log a warning indicating that no dog was found with the specified ID
        loggerInput.LogWarning("No dog found with ID: {DogId}", id);

        // Return a NotFound result to indicate that the dog was not found
        return Results.NotFound();
    }

    // Log the successful retrieval of the dog
    loggerInput.LogInformation("Dog with ID: {DogId} retrieved successfully", id);

    // Return an Ok result with the retrieved dog
    return Results.Ok(dog);
}).RequireAuthorization();

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 14:19:17.972 +01:00 [INF] Attempting to add a new dog: {"Id":0,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:19:18.586 +01:00 [INF] New dog added successfully: {"Id":1,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:19:23.708 +01:00 [INF] Requesting all dogs
2024-02-04 14:19:23.905 +01:00 [INF] Retrieved 1 dogs
2024-02-04 14:20:24.351 +01:00 [INF] Attempting to retrieve dog with ID: 1
2024-02-04 14:20:24.448 +01:00 [INF] Dog with ID: 1 retrieved successfully



DELETE DOG

// Definition Delete Method
app.MapDelete("/dog/{id}", async (int id, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the attempt to delete a dog with a specific ID
    loggerInput.LogInformation("Attempting to delete dog with ID: {DogId}", id);

    // Execute the DeleteDog command to delete the dog with the specified ID
    var deleteOk = await commands.DeleteDog(id);

    // Check if the deletion was successful
    if (deleteOk)
    {
        // Save changes
        await commands.Save();

        // Log the successful deletion of the dog
        loggerInput.LogInformation("Dog with ID: {DogId} deleted successfully", id);

        // Return an Ok result indicating successful deletion
        return Results.Ok();
    }

    // If the dog was not found or could not be deleted, log a warning
    loggerInput.LogWarning("Failed to delete dog with ID: {DogId} - not found", id);

    // Return a NotFound result indicating the dog was not found
    return Results.NotFound();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 14:51:26.606 +01:00 [INF] Attempting to add a new dog: {"Id":0,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:51:27.237 +01:00 [INF] New dog added successfully: {"Id":1,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:51:32.342 +01:00 [INF] Requesting all dogs
2024-02-04 14:51:32.567 +01:00 [INF] Retrieved 1 dogs
2024-02-04 14:52:07.602 +01:00 [INF] Attempting to delete dog with ID: 1
2024-02-04 14:52:07.721 +01:00 [INF] Dog with ID: 1 deleted successfully
2024-02-04 14:52:18.546 +01:00 [INF] Requesting all dogs
2024-02-04 14:52:18.550 +01:00 [WRN] No dogs found
2024-02-04 14:52:28.196 +01:00 [INF] Attempting to delete dog with ID: 1
2024-02-04 14:52:28.202 +01:00 [WRN] Failed to delete dog with ID: 1 - not found



UPDATE DOG

app.MapPut("/dog/{id}", async (int id, Dog dog, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the attempt to update a dog with a specific ID
    loggerInput.LogInformation("Attempting to update dog with ID: {DogId}", id);

    // Execute the UpdateDog command to update the dog's information with the specified ID
    var updateOk = await commands.UpdateDog(dog, id);

    // Check if the update was successful
    if (!updateOk)
    {
        // Log a warning if the dog with the specified ID was not found or could not be updated
        loggerInput.LogWarning("Failed to update dog with ID: {DogId} - not found", id);

        // Return a NotFound result to indicate that the dog was not found or could not be updated
        return Results.NotFound();
    }

    // Log the successful update of the dog
    loggerInput.LogInformation("Dog with ID: {DogId} updated successfully", id);

    // Return a NoContent result indicating successful update
    return Results.NoContent();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 14:59:41.862 +01:00 [INF] Attempting to add a new dog: {"Id":0,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:59:42.458 +01:00 [INF] New dog added successfully: {"Id":1,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 14:59:46.044 +01:00 [INF] Requesting all dogs
2024-02-04 14:59:46.258 +01:00 [INF] Retrieved 1 dogs
2024-02-04 15:00:53.443 +01:00 [INF] Attempting to update dog with ID: 1
2024-02-04 15:00:53.549 +01:00 [INF] Dog with ID: 1 updated successfully
2024-02-04 15:01:01.516 +01:00 [INF] Requesting all dogs
2024-02-04 15:01:01.519 +01:00 [INF] Retrieved 1 dogs



GENERATE TOKEN

// Log the start of the token generation process
    loggerInput.LogInformation("Starting token generation process");

    // Obviously, this authentication way is NOT valid
    // in a real project where, we'd get these from a POST body,
    // and we'd need to ensure everything is securely encrypted.https://www.zoneofdevelopment.com/2023/11/22/minimal-apis-authorization/
    var username = context.Request.Query["username"];
    var password = context.Request.Query["password"];
    
    // Check if the username and password are valid using a mock user store
    if (!MockUserStore.Users.ContainsKey(username) || MockUserStore.Users[username] != password)
    {
        // Log invalid credentials attempt
        loggerInput.LogWarning("Invalid credentials attempt for username: {Username}", username);

        // Return BadRequest if credentials are invalid
        return Results.BadRequest("Invalid credentials");
    }

    // Determine the user's role based on the username (simplified logic)
    var role = username == "admin" ? "Admin" : "Reader";

    // Generate the JWT token using the username, role, and other required parameters
    var generatedToken = TokenGenerator.GenerateToken(jwtSecretKey, tokenExpiryMinutes, role);

    // Log the successful token generation
    loggerInput.LogInformation("Token generated successfully for username: {Username}", username);

    // Return the generated token
    return Results.Ok(generatedToken);

If we run the application, the following will be the result:

[APILOGS20240204.TXT]

2024-02-04 15:10:57.259 +01:00 [INF] Starting token generation process
2024-02-04 15:10:57.426 +01:00 [INF] Token generated successfully for username: admin



The last thing I want to show you, is how to add Serilog to our Business layer which in this case is the DogCommands class :

[DOGCOMMANDS.CS]

using Microsoft.EntityFrameworkCore;
using MinimalAPI.Model;
using Microsoft.Extensions.Logging;

namespace MinimalAPI.Commands;

public class DogCommands:IDogCommands
{
    private readonly DataContext _dataContext;
    private readonly ILogger<DogCommands> _logger;

    public DogCommands(DataContext dataContext, ILogger<DogCommands> logger)
    {
        _dataContext = dataContext;
        _logger = logger;
    }

    public async Task<bool> AddDog(Dog dog)
    {
        try
        {
            _logger.LogInformation($"BLL - Attempting to add a new dog: {dog.Name}");
            await _dataContext.Dogs.AddAsync(dog);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"BLL - Failed to add dog: {dog.Name}");
            return false;
        }
    }

    public async Task<List<Dog>> GetAllDogs()
    {
        _logger.LogInformation("BLL - Retrieving all dogs");
        return await _dataContext.Dogs.AsNoTracking().ToListAsync();
    }

    public async Task<Dog> GetDogById(int id)
    {
        _logger.LogInformation($"BLL - Retrieving  dog with ID={id}");
        return await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);
    }

    public async Task<bool> UpdateDog(Dog dog, int id)
    {
        try
        {
            _logger.LogInformation($"BLL - Attempting to update dog with ID= {dog.Id}");
            var dogInput = await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);

            if(dogInput == null)
            {
                return false;
            }

            dogInput.Name = dog.Name;
            dogInput.Color = dog.Color;
            dogInput.Breed = dog.Breed;

            await _dataContext.SaveChangesAsync();

            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"BLL - Failed to update dog: {dog.Name}");
            throw;
        }
        
    }

    public async Task<bool> DeleteDog(int id)
    {
        try
        {
            _logger.LogInformation($"BLL - Attempting to delete dog with ID= {id}");
            var dogInput = await _dataContext.Dogs.FirstOrDefaultAsync(d => d.Id == id);

            if (dogInput == null)
            {
                return false;
            }

            _dataContext.Dogs.Remove(dogInput);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"BLL - Failed to delete dog with ID: {id}");
            throw;
        }
    }

    public async Task Save()
    {
        try
        {
            _logger.LogInformation($"BLL - Attempting to save data");
            await _dataContext.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"BLL - Failed to save data");
            throw;
        }
        
    }
}

If we run the application, and we run all methods, the following will be the result in the log file:

[APILOGS20240204.TXT]

2024-02-04 15:26:27.153 +01:00 [INF] Attempting to add a new dog: {"Id":0,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 15:26:27.216 +01:00 [INF] BLL - Attempting to add a new dog: Dorian
2024-02-04 15:26:27.701 +01:00 [INF] BLL - Attempting to save data
2024-02-04 15:26:27.750 +01:00 [INF] New dog added successfully: {"Id":1,"Name":"Dorian","Breed":"Rottweiler","Color":"Black","$type":"Dog"}
2024-02-04 15:26:30.753 +01:00 [INF] Requesting all dogs
2024-02-04 15:26:30.755 +01:00 [INF] BLL - Retrieving all dogs
2024-02-04 15:26:30.967 +01:00 [INF] Retrieved 1 dogs
2024-02-04 15:26:32.780 +01:00 [INF] Attempting to retrieve dog with ID: 1
2024-02-04 15:26:32.784 +01:00 [INF] BLL - Retrieving  dog with ID=1
2024-02-04 15:26:32.883 +01:00 [INF] Dog with ID: 1 retrieved successfully
2024-02-04 15:26:35.116 +01:00 [INF] Attempting to update dog with ID: 1
2024-02-04 15:26:35.121 +01:00 [INF] BLL - Attempting to update dog with ID= 1
2024-02-04 15:26:35.136 +01:00 [INF] Dog with ID: 1 updated successfully
2024-02-04 15:26:37.283 +01:00 [INF] Attempting to delete dog with ID: 1
2024-02-04 15:26:37.287 +01:00 [INF] BLL - Attempting to delete dog with ID= 1
2024-02-04 15:26:37.293 +01:00 [INF] BLL - Attempting to save data
2024-02-04 15:26:37.300 +01:00 [INF] Dog with ID: 1 deleted successfully



[PROGRAM.CS]

using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using MinimalAPI;
using MinimalAPI.Commands;
using MinimalAPI.Extensions;
using MinimalAPI.Model;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog
var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .CreateLogger();

Log.Logger = logger;

builder.Host.UseSerilog();

// definition of DataContext
builder.Services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase("DbDog"));
// definition of Dependency Injection
builder.Services.AddScoped<IDogCommands, DogCommands>();

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthorization();

var jwtSecretKey = "password123casdsadsaiodiasdsadas";
var tokenExpiryMinutes = 10;

// Add JWT Authentication
builder.Services.AddJwtAuthentication(jwtSecretKey);

var app = builder.Build();

// Use Authentication and HTTPS redirection
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseAuthorization();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// Definition Get Method
app.MapGet("/dog", async (IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the beginning of the request to get all dogs
    loggerInput.LogInformation("Requesting all dogs");

    // Execute the GetAllDogs command to retrieve all dogs
    var dogs = await commands.GetAllDogs();

    // Check if the result is null or empty
    if (dogs == null || !dogs.Any())
    {
        // Log a warning indicating that no dogs were found
        loggerInput.LogWarning("No dogs found");

        // Return a NotFound result to indicate that no dogs were found
        return Results.NotFound();
    }

    // Log the successful retrieval of dogs, including the count
    loggerInput.LogInformation($"Retrieved {dogs.Count} dogs");

    // Return an Ok result with the list of retrieved dogs
    return Results.Ok(dogs);
}).RequireAuthorization();

// Definition Get{Id} Method
app.MapGet("/dog/{id}", async (int id, IDogCommands commands,  ILogger<Program> loggerInput) =>
{
    // Log the attempt to retrieve a dog with a specific ID
    loggerInput.LogInformation("Attempting to retrieve dog with ID: {DogId}", id);

    // Execute the GetDogById command to retrieve the dog with the specified ID
    var dog = await commands.GetDogById(id);

    // Check if a dog with the specified ID was found
    if (dog == null)
    {
        // Log a warning indicating that no dog was found with the specified ID
        loggerInput.LogWarning("No dog found with ID: {DogId}", id);

        // Return a NotFound result to indicate that the dog was not found
        return Results.NotFound();
    }

    // Log the successful retrieval of the dog
    loggerInput.LogInformation("Dog with ID: {DogId} retrieved successfully", id);

    // Return an Ok result with the retrieved dog
    return Results.Ok(dog);
}).RequireAuthorization();

// Definition Post Method
app.MapPost("/dog", async (Dog dog, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    try
    {
        // Log the attempt to add a new dog with the dog's details
        loggerInput.LogInformation("Attempting to add a new dog: {@Dog}", dog);

        // Attempt to add the dog using the command pattern implementation
        var addSuccess = await commands.AddDog(dog);

        // Check if the addition was successful
        if (!addSuccess)
        {
            // Log a warning if adding the dog failed and return a problem result
            loggerInput.LogWarning("Failed to add a new dog: {@Dog}", dog);
            return Results.Problem("Failed to add a new dog.");
        }

        // Save changes to the database
        await commands.Save();

        // Log the successful addition of the dog
        loggerInput.LogInformation("New dog added successfully: {@Dog}", dog);

        // Return an OK result upon successful addition
        return Results.Ok();
    }
    catch (Exception ex)
    {
        // Log any exceptions that occur during the process
        loggerInput.LogError(ex, "Error occurred while adding a new dog: {@Dog}", dog);

        // Return a problem result in case of exceptions
        return Results.Problem("An error occurred while processing your request.");
    }
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });

// Definition Put Method
app.MapPut("/dog/{id}", async (int id, Dog dog, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the attempt to update a dog with a specific ID
    loggerInput.LogInformation("Attempting to update dog with ID: {DogId}", id);

    // Execute the UpdateDog command to update the dog's information with the specified ID
    var updateOk = await commands.UpdateDog(dog, id);

    // Check if the update was successful
    if (!updateOk)
    {
        // Log a warning if the dog with the specified ID was not found or could not be updated
        loggerInput.LogWarning("Failed to update dog with ID: {DogId} - not found", id);

        // Return a NotFound result to indicate that the dog was not found or could not be updated
        return Results.NotFound();
    }

    // Log the successful update of the dog
    loggerInput.LogInformation("Dog with ID: {DogId} updated successfully", id);

    // Return a NoContent result indicating successful update
    return Results.NoContent();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });

// Definition Delete Method
app.MapDelete("/dog/{id}", async (int id, IDogCommands commands, ILogger<Program> loggerInput) =>
{
    // Log the attempt to delete a dog with a specific ID
    loggerInput.LogInformation("Attempting to delete dog with ID: {DogId}", id);

    // Execute the DeleteDog command to delete the dog with the specified ID
    var deleteOk = await commands.DeleteDog(id);

    // Check if the deletion was successful
    if (deleteOk)
    {
        // Save changes
        await commands.Save();

        // Log the successful deletion of the dog
        loggerInput.LogInformation("Dog with ID: {DogId} deleted successfully", id);

        // Return an Ok result indicating successful deletion
        return Results.Ok();
    }

    // If the dog was not found or could not be deleted, log a warning
    loggerInput.LogWarning("Failed to delete dog with ID: {DogId} - not found", id);

    // Return a NotFound result indicating the dog was not found
    return Results.NotFound();
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });


// POST /token to generate JWT token
app.MapPost("/token", async (HttpContext context, ILogger<Program> loggerInput) =>
{
    // Log the start of the token generation process
    loggerInput.LogInformation("Starting token generation process");

    // Obviously, this authentication way is NOT valid
    // in a real project where, we'd get these from a POST body,
    // and we'd need to ensure everything is securely encrypted.https://www.zoneofdevelopment.com/2023/11/22/minimal-apis-authorization/
    var username = context.Request.Query["username"];
    var password = context.Request.Query["password"];
    
    // Check if the username and password are valid using a mock user store
    if (!MockUserStore.Users.ContainsKey(username) || MockUserStore.Users[username] != password)
    {
        // Log invalid credentials attempt
        loggerInput.LogWarning("Invalid credentials attempt for username: {Username}", username);

        // Return BadRequest if credentials are invalid
        return Results.BadRequest("Invalid credentials");
    }

    // Determine the user's role based on the username (simplified logic)
    var role = username == "admin" ? "Admin" : "Reader";

    // Generate the JWT token using the username, role, and other required parameters
    var generatedToken = TokenGenerator.GenerateToken(jwtSecretKey, tokenExpiryMinutes, role);

    // Log the successful token generation
    loggerInput.LogInformation("Token generated successfully for username: {Username}", username);

    // Return the generated token
    return Results.Ok(generatedToken);
}).AllowAnonymous();

app.Run();



Leave a Reply

Your email address will not be published. Required fields are marked *