Skip to content

Commit

Permalink
Code(Cross-cutting concerns): Integrate Serilog into the project
Browse files Browse the repository at this point in the history
  • Loading branch information
ktutak1337 committed Mar 11, 2024
1 parent 762c34d commit 83c1e02
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 11 deletions.
3 changes: 3 additions & 0 deletions src/Server/StellarChat.Server.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StellarChat.Shared.Infrastructure.Exceptions;
using StellarChat.Shared.Infrastructure.Contexts;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -7,6 +8,7 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddErrorHandling();
builder.Services.AddContext();

var app = builder.Build();

Expand All @@ -19,6 +21,7 @@

app.UseHttpsRedirection();
app.UseErrorHandling();
app.UseContext();

var summaries = new[]
{
Expand Down
52 changes: 46 additions & 6 deletions src/Server/StellarChat.Server.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,49 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
"app": {
"name": "Chat Stellar API",
"version": "0.0.1"
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"serilog": {
"level": "information",
"overrides": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning"
},
"excludePaths": [
"/",
"/metrics",
"/ping"
],
"excludeProperties": [
"api_key",
"access_key",
"apiKey",
"apiSecret",
"clientId",
"clientSecret",
"connectionString",
"password",
"email",
"login",
"secret",
"token",
"organizationId"
],
"console": {
"enabled": true
},
"file": {
"enabled": false,
"path": "logs/logs.txt",
"interval": "day"
},
"seq": {
"enabled": true,
"url": "http://localhost:5341",
"apiKey": "secret"
},
"tags": {}
}
}
11 changes: 11 additions & 0 deletions src/Shared/StellarChat.Shared.Abstractions/Contexts/IContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace StellarChat.Shared.Abstractions.Contexts;

public interface IContext
{
Guid RequestId { get; }
Guid CorrelationId { get; }
string TraceId { get; }
string IpAddress { get; }
string UserAgent { get; }
IIdentityContext Identity { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace StellarChat.Shared.Abstractions.Contexts;

public interface IIdentityContext
{
bool IsAuthenticated { get; }
public Guid Id { get; }
string Role { get; }
Dictionary<string, IEnumerable<string>> Claims { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions src/Shared/StellarChat.Shared.Infrastructure/AppOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace StellarChat.Shared.Infrastructure;

internal class AppOptions
{
public string Name { get; set; } = string.Empty;
public string Instance { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
}
36 changes: 36 additions & 0 deletions src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using StellarChat.Shared.Abstractions.Contexts;

namespace StellarChat.Shared.Infrastructure.Contexts;

public class Context : IContext
{
public Guid RequestId { get; } = Guid.NewGuid();
public Guid CorrelationId { get; }
public string TraceId { get; }
public string IpAddress { get; }
public string UserAgent { get; }
public IIdentityContext Identity { get; }

public Context() : this(Guid.NewGuid(), $"{Guid.NewGuid():N}", null)

Check warning on line 15 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
}

public Context(HttpContext context) : this(context.TryGetCorrelationId(), context.TraceIdentifier,
new IdentityContext(context.User), context.GetUserIpAddress(),
context.Request.Headers["user-agent"])

Check warning on line 21 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'userAgent' in 'Context.Context(Guid? correlationId, string traceId, IIdentityContext identity = null, string ipAddress = null, string userAgent = null)'.
{
}

public Context(Guid? correlationId, string traceId, IIdentityContext identity = null, string ipAddress = null,

Check warning on line 25 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 25 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
string userAgent = null)

Check warning on line 26 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/Context.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
CorrelationId = correlationId ?? Guid.NewGuid();
TraceId = traceId;
Identity = identity ?? IdentityContext.Empty;
IpAddress = ipAddress;
UserAgent = userAgent;
}

public static IContext Empty => new Context();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using StellarChat.Shared.Abstractions.Contexts;

namespace StellarChat.Shared.Infrastructure.Contexts;

public sealed class ContextAccessor
{
private static readonly AsyncLocal<ContextHolder> Holder = new();

public IContext Context
{
get => Holder.Value?.Context;

Check warning on line 11 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/ContextAccessor.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
set
{
var holder = Holder.Value;
if (holder != null)
{
holder.Context = null;

Check warning on line 17 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/ContextAccessor.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
}

if (value != null)
{
Holder.Value = new ContextHolder { Context = value };
}
}
}

private class ContextHolder
{
public IContext Context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace StellarChat.Shared.Infrastructure.Contexts;

public static class Extensions
{
public static IServiceCollection AddContext(this IServiceCollection services)
{
services.AddSingleton<ContextAccessor>();
services.AddTransient(sp => sp.GetRequiredService<ContextAccessor>().Context);

return services;
}

public static IApplicationBuilder UseContext(this IApplicationBuilder app)
{
app.Use((ctx, next) =>
{
ctx.RequestServices.GetRequiredService<ContextAccessor>().Context = new Context(ctx);

return next();
});

return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using StellarChat.Shared.Abstractions.Contexts;
using System.Security.Claims;

namespace StellarChat.Shared.Infrastructure.Contexts;

public class IdentityContext : IIdentityContext
{
public bool IsAuthenticated { get; }
public Guid Id { get; }
public string? Role { get; }

Check warning on line 10 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/IdentityContext.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of reference types in return type of 'string? IdentityContext.Role.get' doesn't match implicitly implemented member 'string IIdentityContext.Role.get' (possibly because of nullability attributes).
public Dictionary<string, IEnumerable<string>>? Claims { get; }

Check warning on line 11 in src/Shared/StellarChat.Shared.Infrastructure/Contexts/IdentityContext.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of reference types in return type of 'Dictionary<string, IEnumerable<string>>? IdentityContext.Claims.get' doesn't match implicitly implemented member 'Dictionary<string, IEnumerable<string>> IIdentityContext.Claims.get' (possibly because of nullability attributes).

private IdentityContext() { }

public IdentityContext(Guid? id)
{
Id = id ?? Guid.Empty;
IsAuthenticated = id.HasValue;
Role = null;
Claims = null;
}

public IdentityContext(ClaimsPrincipal? principal)
{
if (principal?.Identity is null || string.IsNullOrWhiteSpace(principal.Identity?.Name))
{
IsAuthenticated = false;
Id = Guid.Empty;
Role = null;
Claims = null;
return;
}

IsAuthenticated = principal.Identity.IsAuthenticated;
Id = IsAuthenticated ? Guid.Parse(principal.Identity.Name) : Guid.Empty;
Role = principal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Role)?.Value;
Claims = principal.Claims.GroupBy(x => x.Type)
.ToDictionary(x => x.Key, x => x.Select(c => c.Value.ToString()));
}

public static IIdentityContext Empty => new IdentityContext();
}

54 changes: 53 additions & 1 deletion src/Shared/StellarChat.Shared.Infrastructure/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System.Text.RegularExpressions;

namespace StellarChat.Shared.Infrastructure;

public static class Extensions
{
private const string CorrelationIdKey = "correlation-id";

public static string ToSnakeCase(this string input)
=> Regex.Replace(
Regex.Replace(
Regex.Replace(input, @"([\p{Lu}]+)([\p{Lu}][\p{Ll}])", "$1_$2"), @"([\p{Ll}\d])([\p{Lu}])", "$1_$2"), @"[-\s]", "_").ToLower();

public static bool IsEmpty(this string value)
=> string.IsNullOrWhiteSpace(value);

public static bool IsNotEmpty(this string value)
=> !value.IsEmpty();

public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
=> app.Use((ctx, next) =>
{
ctx.Items.Add(CorrelationIdKey, Guid.NewGuid());
return next();
});

public static T BindOptions<T>(this IConfiguration configuration, string sectionName) where T : new()
=> BindOptions<T>(configuration.GetSection(sectionName));

public static T BindOptions<T>(this IConfigurationSection section) where T : new()
{
var options = new T();
section.Bind(options);
return options;
}

public static Guid? TryGetCorrelationId(this HttpContext context)
=> context.Items.TryGetValue(CorrelationIdKey, out var id) ? (Guid?)id : null;

public static string GetUserIpAddress(this HttpContext context)
{
if (context is null)
{
return string.Empty;
}

var ipAddress = context.Connection.RemoteIpAddress?.ToString();
if (context.Request.Headers.TryGetValue("x-forwarded-for", out var forwardedFor))
{
var ipAddresses = forwardedFor.ToString().Split(",", StringSplitOptions.RemoveEmptyEntries);

if (ipAddresses.Any())
{
ipAddress = ipAddresses[0];
}
}

return ipAddress ?? string.Empty;
}
}
Loading

0 comments on commit 83c1e02

Please sign in to comment.