Skip to content

Commit

Permalink
Logging config cleanup and resolves #562
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-bstein committed Feb 17, 2025
1 parent 6eabf6a commit c7f895a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 52 deletions.
92 changes: 52 additions & 40 deletions src/Gameboard.Api/Extensions/WebApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,46 +68,6 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett
{
var services = builder.Services;

// serilog config
builder.Host.UseSerilog();

Serilog.Debugging.SelfLog.Enable(Console.Error);
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Is(settings.Logging.MinimumLogLevel)
.WriteTo.Console(theme: AnsiConsoleTheme.Code);

// normally, you'd do this in an appsettings.json and just rely on built-in config
// assembly stuff, but we distribute with a helm chart and a weird conf format, so
// we need to manually set up the log levels
foreach (var logNamespace in settings.Logging.NamespacesErrorLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Error);
}

foreach (var logNamespace in settings.Logging.NamespacesFatalLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Fatal);
}

foreach (var logNamespace in settings.Logging.NamespacesInfoLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Information);
}

foreach (var logNamespace in settings.Logging.NamespacesWarningLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Warning);
}

// set up sinks on demand
if (settings.Logging.SeqInstanceUrl.IsNotEmpty())
{
loggerConfiguration = loggerConfiguration.WriteTo.Seq(settings.Logging.SeqInstanceUrl, apiKey: settings.Logging.SeqInstanceApiKey);
}

// weirdly, this really does appear to be the way to replace the default logger with Serilog 🤷
Log.Logger = loggerConfiguration.CreateLogger();

services
.AddMvc()
.AddGameboardJsonOptions();
Expand All @@ -124,6 +84,8 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett
.SetApplicationName(AppConstants.DataProtectionPurpose)
.PersistKeys(() => settings.Cache);

builder.AddGameboardSerilog(settings);

services
.AddSingleton(_ => settings.Core)
.AddSingleton(_ => settings.Crucible)
Expand All @@ -149,4 +111,54 @@ public static void ConfigureServices(this WebApplicationBuilder builder, AppSett
services.AddConfiguredAuthentication(settings.Oidc, settings.ApiKey, builder.Environment);
services.AddConfiguredAuthorization();
}

private static WebApplicationBuilder AddGameboardSerilog(this WebApplicationBuilder builder, AppSettings settings)
{
// SERILOG CONFIG
// Gameboard uses Serilog, which is awesome, because Serilog is awesome. By default, it just
// writes to the console sink, which you can ingest with any log ingester (and is useful if you
// choose to monitor the output of its pod in a K8s-style scenario). But if you want richer logging,
// you can add a Seq instance using its configuration so you get nice metadata like the userID
// and name for API requests. Want to use a non-Seq sink? We get it. PR us and let's talk about it.
builder.Host.UseSerilog();

Serilog.Debugging.SelfLog.Enable(Console.Error);
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Is(settings.Logging.MinimumLogLevel)
.WriteTo.Console(theme: AnsiConsoleTheme.Code);

// normally, you'd do this in an appsettings.json and just rely on built-in config
// assembly stuff, but we distribute with a helm chart and a weird conf format, so
// we need to manually set up the log levels
foreach (var logNamespace in settings.Logging.NamespacesErrorLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Error);
}

foreach (var logNamespace in settings.Logging.NamespacesFatalLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Fatal);
}

foreach (var logNamespace in settings.Logging.NamespacesInfoLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Information);
}

foreach (var logNamespace in settings.Logging.NamespacesWarningLevel)
{
loggerConfiguration = loggerConfiguration.MinimumLevel.Override(logNamespace, LogEventLevel.Warning);
}

// set up sinks on demand
if (settings.Logging.SeqInstanceUrl.IsNotEmpty())
{
loggerConfiguration = loggerConfiguration.WriteTo.Seq(settings.Logging.SeqInstanceUrl, apiKey: settings.Logging.SeqInstanceApiKey);
}

// weirdly, this really does appear to be the way to replace the default logger with Serilog 🤷
Log.Logger = loggerConfiguration.CreateLogger();

return builder;
}
}
19 changes: 7 additions & 12 deletions src/Gameboard.Api/Features/Game/GameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public interface IGameService
Task<SessionForecast[]> SessionForecast(string id);
Task<Data.Game> Update(ChangedGame account);
Task UpdateImage(string id, string type, string filename);
Task<bool> UserIsTeamPlayer(string uid, string tid);
}

public class GameService
Expand All @@ -38,11 +37,13 @@ public class GameService
IMapper mapper,
CoreOptions options,
Defaults defaults,
IGuidService guids,
INowService nowService,
IStore store
) : _Service(logger, mapper, options), IGameService
{
private readonly Defaults _defaults = defaults;
private readonly IGuidService _guids = guids;
private readonly INowService _now = nowService;
private readonly IStore _store = store;

Expand Down Expand Up @@ -217,16 +218,6 @@ public async Task UpdateImage(string id, string type, string filename)
public Task<bool> IsUserPlaying(string gameId, string userId)
=> _store.AnyAsync<Data.Player>(p => p.GameId == gameId && p.UserId == userId, CancellationToken.None);

public async Task<bool> UserIsTeamPlayer(string uid, string tid)
{
bool authd = await _store.AnyAsync<Data.User>(u =>
u.Id == uid &&
u.Enrollments.Any(e => e.TeamId == tid)
, CancellationToken.None);

return authd;
}

public async Task DeleteGameCardImage(string gameId)
{
if (!await _store.WithNoTracking<Data.Game>().AnyAsync(g => g.Id == gameId))
Expand All @@ -248,6 +239,10 @@ public async Task<UploadedFile> SaveGameCardImage(string gameId, IFormFile file)
if (!await _store.WithNoTracking<Data.Game>().AnyAsync(g => g.Id == gameId))
throw new ResourceNotFound<Data.Game>(gameId);

// we currently intentionally leave the old image around for quasi-logging purposes,
// but we could delete if desired here by getting the path from the Game entity.
// We generate a semi-random name for the new file in GetGameCardFileNameBase to bypass
// network-level caching.
var fileName = $"{GetGameCardFileNameBase(gameId)}{Path.GetExtension(file.FileName.ToLower())}";
var path = Path.Combine(Options.ImageFolder, fileName);

Expand All @@ -259,7 +254,7 @@ public async Task<UploadedFile> SaveGameCardImage(string gameId, IFormFile file)
}

private string GetGameCardFileNameBase(string gameId)
=> $"{gameId.ToLower()}_card";
=> $"{gameId.ToLower()}_card_{_guids.Generate()[..6]}";

private IQueryable<Data.Game> BuildSearchQuery(GameSearchFilter model, bool canViewUnpublished = false)
{
Expand Down

0 comments on commit c7f895a

Please sign in to comment.