Skip to content

Commit 95ae2e6

Browse files
author
Ahmad El Lahib
committed
Add OpenTelemetry and change logging to use templates
1 parent 3addefb commit 95ae2e6

File tree

18 files changed

+212
-157
lines changed

18 files changed

+212
-157
lines changed

src/CourseLibrary/CourseLibrary.API/Brokers/Loggings/ILoggingBroker.cs renamed to src/CourseLibrary/CourseLibrary.API/Brokers/Logging/ILoggingBroker.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace CourseLibrary.API.Brokers.Loggings;
1+
namespace CourseLibrary.API.Brokers.Logging;
22

33
public interface ILoggingBroker<T> where T : class
44
{
@@ -12,7 +12,7 @@ public interface ILoggingBroker<T> where T : class
1212

1313
void LogWarning(string message, params object?[] args);
1414

15-
void LogError(Exception exception, string instance);
15+
void LogError(string scheme, string requestMethod, string requestPath, Exception exception);
1616

17-
void LogCritical(string instance, Exception exception);
17+
void LogCritical(string scheme, string requestMethod, string requestPath, Exception exception);
1818
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace CourseLibrary.API.Brokers.Logging;
2+
3+
internal sealed class LoggingBroker<T> : ILoggingBroker<T> where T : class
4+
{
5+
private readonly ILogger<T> _logger;
6+
7+
public LoggingBroker(ILogger<T> logger) =>
8+
_logger = logger;
9+
10+
public bool IsEnabled(LogLevel logLevel) =>
11+
_logger.IsEnabled(logLevel);
12+
13+
public void LogDebug(string message) =>
14+
_logger.LogDebug(message);
15+
16+
public void LogTrace(string message) =>
17+
_logger.LogTrace(message);
18+
19+
public void LogInformation(string message, params object?[] args) =>
20+
_logger.LogInformation(message, args);
21+
22+
public void LogWarning(string message, params object?[] args) =>
23+
_logger.LogWarning(message, args);
24+
25+
public void LogError(string scheme, string requestMethod, string requestPath, Exception exception)
26+
{
27+
string message = exception.Message;
28+
string innerExceptionMessage = exception.InnerException?.Message ?? "No inner exception";
29+
30+
string msgTemplate = scheme + " {RequestMethod} {RequestPath} {Message} Inner Exception: {InnerException}";
31+
_logger.LogError(exception, msgTemplate, requestMethod, requestPath, message, innerExceptionMessage);
32+
}
33+
34+
public void LogCritical(string scheme, string requestMethod, string requestPath, Exception exception)
35+
{
36+
string message = exception.Message;
37+
string innerExceptionMessage = exception.InnerException?.Message ?? "No inner exception";
38+
39+
string msgTemplate = scheme + " {RequestMethod} {RequestPath} {Message} Inner Exception: {InnerException}";
40+
_logger.LogCritical(exception, msgTemplate, requestMethod, requestPath, message, innerExceptionMessage);
41+
}
42+
}

src/CourseLibrary/CourseLibrary.API/Brokers/Loggings/LoggingBroker.cs

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/CourseLibrary/CourseLibrary.API/Controllers/BaseController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using CourseLibrary.API.Brokers.Loggings;
1+
using CourseLibrary.API.Brokers.Logging;
22
using CourseLibrary.API.Models.Exceptions;
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.AspNetCore.Mvc.ModelBinding;
Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
2-
<PropertyGroup>
3-
<TargetFramework>net8.0</TargetFramework>
4-
<Nullable>enable</Nullable>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
7-
<AssemblyName>CourseLibrary</AssemblyName>
8-
</PropertyGroup>
9-
<ItemGroup>
10-
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
11-
<PackageReference Include="Bogus" Version="35.6.1" />
12-
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
13-
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
14-
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
15-
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
16-
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
17-
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
18-
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
19-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.5.1" />
20-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
21-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
22-
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
23-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
24-
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
25-
<PrivateAssets>all</PrivateAssets>
26-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
27-
</PackageReference>
28-
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
29-
<PackageReference Include="System.Text.Json" Version="8.0.5" />
30-
</ItemGroup>
31-
<ItemGroup>
32-
<Folder Include="Migrations\" />
33-
</ItemGroup>
34-
<ItemGroup>
35-
<InternalsVisibleTo Include="$(AssemblyName).Tests.Unit" />
36-
<InternalsVisibleTo Include="$(AssemblyName).Tests.Integration" />
37-
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
38-
</ItemGroup>
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
7+
<AssemblyName>CourseLibrary</AssemblyName>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
11+
<PackageReference Include="Bogus" Version="35.6.1" />
12+
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
13+
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.10.0" /><!--Demo purpose-->
14+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
15+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
16+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.10.1" />
17+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
18+
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
19+
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
20+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.5.1" />
21+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
22+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
23+
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
24+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
25+
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
26+
<PrivateAssets>all</PrivateAssets>
27+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
28+
</PackageReference>
29+
30+
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
31+
<PackageReference Include="System.Text.Json" Version="8.0.5" />
32+
</ItemGroup>
33+
<ItemGroup>
34+
<Folder Include="Migrations\" />
35+
</ItemGroup>
36+
<ItemGroup>
37+
<InternalsVisibleTo Include="$(AssemblyName).Tests.Unit" />
38+
<InternalsVisibleTo Include="$(AssemblyName).Tests.Integration" />
39+
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
40+
</ItemGroup>
3941
</Project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Diagnostics.Metrics;
2+
3+
namespace CourseLibrary.API.Diagnostics;
4+
5+
public static class DiagnosticsConfig
6+
{
7+
public const string ServiceName = "CourseLibrary";
8+
public const string ServiceVersion = "1.0.0";
9+
10+
public static Meter Meter = new(ServiceName);
11+
12+
// custom metrics
13+
public static Counter<int> CoursesVisited = Meter.CreateCounter<int>("courses.visited.count");
14+
}

src/CourseLibrary/CourseLibrary.API/Filters/EndpointElapsedTimeFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using CourseLibrary.API.Brokers.Loggings;
1+
using CourseLibrary.API.Brokers.Logging;
22
using Microsoft.AspNetCore.Mvc.Filters;
33
using System.Diagnostics;
44

@@ -21,7 +21,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
2121
await next();
2222

2323
// Do something after the action executes.
24-
_logger.LogInformation("Elapsed time for '{RequestMethod}' '{RequestPath}' response: {ElapsedTime} ms",
24+
_logger.LogInformation(context.HttpContext.Request.Scheme.ToUpperInvariant() + " {RequestMethod} {RequestPath} responded in {ElapsedTime} ms",
2525
context.HttpContext.Request.Method,
2626
context.HttpContext.Request.Path,
2727
Stopwatch.GetElapsedTime(startTime).TotalMilliseconds);

src/CourseLibrary/CourseLibrary.API/Filters/GlobalExceptionFilter.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Microsoft.AspNetCore.Mvc.Filters;
22
using Microsoft.AspNetCore.Mvc;
33
using System.Net;
4-
using CourseLibrary.API.Brokers.Loggings;
4+
using CourseLibrary.API.Brokers.Logging;
55
using CourseLibrary.API.Models.Exceptions;
66

77
namespace CourseLibrary.API.Filters;
@@ -20,14 +20,17 @@ public GlobalExceptionFilter(IWebHostEnvironment env, ILoggingBroker<GlobalExcep
2020
public void OnException(ExceptionContext context)
2121
{
2222
Exception exception = context.Exception;
23-
string instance = $"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}";
23+
string scheme = context.HttpContext.Request.Scheme;
24+
string requestMethod = context.HttpContext.Request.Method;
25+
string requestPath = context.HttpContext.Request.Path;
26+
string warningMsgTemplate = context.HttpContext.Request.Scheme.ToUpperInvariant() + " {RequestMethod} {RequestPath} {Message}";
2427

2528
ProblemDetails problemDetails = new()
2629
{
2730
Title = "An error occurred.",
2831
Status = (int)HttpStatusCode.InternalServerError,
2932
Detail = "An unexpected error occurred.",
30-
Instance = instance
33+
Instance = $"{requestMethod} {requestPath}"
3134
};
3235

3336
switch (exception)
@@ -42,32 +45,32 @@ public void OnException(ExceptionContext context)
4245
problemDetails.Status = StatusCodes.Status401Unauthorized;
4346
problemDetails.Title = "Unauthorized";
4447
problemDetails.Detail = "You are not authorized.";
45-
_logger.LogWarning("{instance} {message}", instance, exception.Message);
48+
_logger.LogWarning(warningMsgTemplate, requestMethod, requestPath, exception.Message);
4649
break;
4750
case NotFoundEntityException:
4851
problemDetails.Status = StatusCodes.Status404NotFound;
4952
problemDetails.Title = "Not Found";
5053
problemDetails.Detail = "Resource not found.";
51-
_logger.LogWarning("{instance} {message}", instance, exception.Message);
54+
_logger.LogWarning(warningMsgTemplate, requestMethod, requestPath, exception.Message);
5255
break;
5356
case ResourceParametersException:
5457
case CategoryWithSameNameAlreadyExistsException:
5558
problemDetails.Status = StatusCodes.Status400BadRequest;
5659
problemDetails.Title = "Bad Request";
5760
problemDetails.Detail = exception.Message;
58-
_logger.LogWarning("{instance} {message}", instance, exception.Message);
61+
_logger.LogWarning(warningMsgTemplate, requestMethod, requestPath, exception.Message);
5962
break;
6063
case InvalidParameterException:
6164
problemDetails.Status = StatusCodes.Status400BadRequest;
6265
problemDetails.Title = "Bad Request";
6366
problemDetails.Detail = exception.Message;
64-
_logger.LogError(exception, instance);
67+
_logger.LogError(scheme,requestMethod, requestPath, exception);
6568
break;
6669
case InvalidOperationException:
6770
problemDetails.Status = StatusCodes.Status400BadRequest;
6871
problemDetails.Title = "Bad Request";
6972
problemDetails.Detail = "The request is invalid.";
70-
_logger.LogError(exception, instance);
73+
_logger.LogError(scheme, requestMethod, requestPath, exception);
7174
break;
7275
// Add more specific exception handling here
7376
default:
@@ -76,7 +79,7 @@ public void OnException(ExceptionContext context)
7679
problemDetails.Detail = exception.Message;
7780
}
7881

79-
_logger.LogCritical(instance, exception);
82+
_logger.LogCritical(scheme, requestMethod, requestPath, exception);
8083
break;
8184
}
8285

0 commit comments

Comments
 (0)