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();