feat(core): add optional SQL query logging

This commit is contained in:
sam 2024-05-22 02:31:05 +02:00
parent b95fb76cd4
commit 6aed05af06
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
8 changed files with 56 additions and 31 deletions

View file

@ -1,6 +1,7 @@
using Foxchat.Chat.Database.Models; using Foxchat.Chat.Database.Models;
using Foxchat.Core; using Foxchat.Core;
using Foxchat.Core.Database; using Foxchat.Core.Database;
using Foxchat.Core.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;
using Npgsql; using Npgsql;
@ -10,6 +11,7 @@ namespace Foxchat.Chat.Database;
public class ChatContext : IDatabaseContext public class ChatContext : IDatabaseContext
{ {
private readonly NpgsqlDataSource _dataSource; private readonly NpgsqlDataSource _dataSource;
private readonly ILoggerFactory? _loggerFactory;
public override DbSet<Instance> Instance { get; set; } public override DbSet<Instance> Instance { get; set; }
public DbSet<IdentityInstance> IdentityInstances { get; set; } public DbSet<IdentityInstance> IdentityInstances { get; set; }
@ -18,7 +20,7 @@ public class ChatContext : IDatabaseContext
public DbSet<Channel> Channels { get; set; } public DbSet<Channel> Channels { get; set; }
public DbSet<Message> Messages { get; set; } public DbSet<Message> Messages { get; set; }
public ChatContext(InstanceConfig config) public ChatContext(InstanceConfig config, ILoggerFactory? loggerFactory)
{ {
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url) var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
{ {
@ -29,12 +31,14 @@ public class ChatContext : IDatabaseContext
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
dataSourceBuilder.UseNodaTime(); dataSourceBuilder.UseNodaTime();
_dataSource = dataSourceBuilder.Build(); _dataSource = dataSourceBuilder.Build();
_loggerFactory = loggerFactory;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder => optionsBuilder
.UseNpgsql(_dataSource, o => o.UseNodaTime()) .UseNpgsql(_dataSource, o => o.UseNodaTime())
.UseSnakeCaseNamingConvention(); .UseSnakeCaseNamingConvention()
.UseLoggerFactory(_loggerFactory);
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{ {
@ -73,6 +77,6 @@ public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<Chat
// Get the configuration as our config class // Get the configuration as our config class
.Get<InstanceConfig>() ?? new(); .Get<InstanceConfig>() ?? new();
return new ChatContext(config); return new ChatContext(config, null);
} }
} }

View file

@ -4,13 +4,14 @@ using Foxchat.Core;
using Foxchat.Chat; using Foxchat.Chat;
using Foxchat.Chat.Database; using Foxchat.Chat.Database;
using Foxchat.Chat.Extensions; using Foxchat.Chat.Extensions;
using Foxchat.Core.Extensions;
using Newtonsoft.Json; using Newtonsoft.Json;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var config = builder.AddConfiguration<InstanceConfig>("chat.ini"); var config = builder.AddConfiguration<InstanceConfig>("chat.ini");
builder.AddSerilog(config.LogEventLevel); builder.AddSerilog();
await BuildInfo.ReadBuildInfo(); await BuildInfo.ReadBuildInfo();
Log.Information("Starting Foxchat.Chat {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash); Log.Information("Starting Foxchat.Chat {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);

View file

@ -2,9 +2,6 @@ Host = localhost
Port = 7610 Port = 7610
Domain = chat.fox.localhost Domain = chat.fox.localhost
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
LogEventLevel = Debug
[Database] [Database]
; The database URL in ADO.NET format. ; The database URL in ADO.NET format.
Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=password" Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=password"
@ -13,3 +10,11 @@ Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=passwor
Timeout = 5 Timeout = 5
; The maximum number of open connections. Defaults to 50. ; The maximum number of open connections. Defaults to 50.
MaxPoolSize = 500 MaxPoolSize = 500
[Logging]
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
LogEventLevel = Debug
; Whether to log SQL queries.
LogQueries = true
; Optional logging to Seq
SeqLogUrl = http://localhost:5341

View file

@ -11,9 +11,7 @@ public class CoreConfig
public string Address => $"{(Secure ? "https" : "http")}://{Host}:{Port}"; public string Address => $"{(Secure ? "https" : "http")}://{Host}:{Port}";
public LogEventLevel LogEventLevel { get; set; } = LogEventLevel.Debug; public LoggingConfig Logging { get; set; } = new();
public string? SeqLogUrl { get; set; }
public DatabaseConfig Database { get; set; } = new(); public DatabaseConfig Database { get; set; } = new();
public class DatabaseConfig public class DatabaseConfig
@ -22,4 +20,11 @@ public class CoreConfig
public int? Timeout { get; set; } public int? Timeout { get; set; }
public int? MaxPoolSize { get; set; } public int? MaxPoolSize { get; set; }
} }
}
public class LoggingConfig
{
public LogEventLevel LogEventLevel { get; set; } = LogEventLevel.Debug;
public string? SeqLogUrl { get; set; }
public bool LogQueries { get; set; } = false;
}
}

View file

@ -7,30 +7,32 @@ using NodaTime;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
namespace Foxchat.Core; namespace Foxchat.Core.Extensions;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
/// <summary> /// <summary>
/// Adds Serilog to this service collection. This method also initializes Serilog so it should be called as early as possible, before any log calls. /// Adds Serilog to this service collection. This method also initializes Serilog so it should be called as early as possible, before any log calls.
/// </summary> /// </summary>
public static void AddSerilog(this WebApplicationBuilder builder, LogEventLevel level) public static void AddSerilog(this WebApplicationBuilder builder)
{ {
var config = builder.Configuration.Get<CoreConfig>() ?? new(); var config = builder.Configuration.Get<CoreConfig>() ?? new();
var logCfg = new LoggerConfiguration() var logCfg = new LoggerConfiguration()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.MinimumLevel.Is(level) .MinimumLevel.Is(config.Logging.LogEventLevel)
// ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead. // ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead.
// Serilog doesn't disable the built in logs so we do it here. // Serilog doesn't disable the built-in logs, so we do it here.
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command",
config.Logging.LogQueries ? LogEventLevel.Information : LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
.WriteTo.Console(); .WriteTo.Console();
if (config.SeqLogUrl != null) if (config.Logging.SeqLogUrl != null)
logCfg.WriteTo.Seq(config.SeqLogUrl, restrictedToMinimumLevel: LogEventLevel.Verbose); logCfg.WriteTo.Seq(config.Logging.SeqLogUrl, restrictedToMinimumLevel: LogEventLevel.Verbose);
Log.Logger = logCfg.CreateLogger(); Log.Logger = logCfg.CreateLogger();
@ -54,9 +56,9 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
public static T AddConfiguration<T>(this WebApplicationBuilder builder, string? configFile = null) where T : class, new() public static T AddConfiguration<T>(this WebApplicationBuilder builder, string? configFile = null)
where T : class, new()
{ {
builder.Configuration.Sources.Clear(); builder.Configuration.Sources.Clear();
builder.Configuration.AddConfiguration(configFile); builder.Configuration.AddConfiguration(configFile);
@ -76,4 +78,4 @@ public static class ServiceCollectionExtensions
.AddIniFile(file, optional: false, reloadOnChange: true) .AddIniFile(file, optional: false, reloadOnChange: true)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
} }
} }

View file

@ -1,5 +1,6 @@
using Foxchat.Core; using Foxchat.Core;
using Foxchat.Core.Database; using Foxchat.Core.Database;
using Foxchat.Core.Extensions;
using Foxchat.Identity.Database.Models; using Foxchat.Identity.Database.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;
@ -10,6 +11,7 @@ namespace Foxchat.Identity.Database;
public class IdentityContext : IDatabaseContext public class IdentityContext : IDatabaseContext
{ {
private readonly NpgsqlDataSource _dataSource; private readonly NpgsqlDataSource _dataSource;
private readonly ILoggerFactory? _loggerFactory;
public override DbSet<Instance> Instance { get; set; } public override DbSet<Instance> Instance { get; set; }
public DbSet<Account> Accounts { get; set; } public DbSet<Account> Accounts { get; set; }
@ -18,7 +20,7 @@ public class IdentityContext : IDatabaseContext
public DbSet<Token> Tokens { get; set; } public DbSet<Token> Tokens { get; set; }
public DbSet<GuildAccount> GuildAccounts { get; set; } public DbSet<GuildAccount> GuildAccounts { get; set; }
public IdentityContext(InstanceConfig config) public IdentityContext(InstanceConfig config, ILoggerFactory? loggerFactory)
{ {
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url) var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
{ {
@ -29,12 +31,14 @@ public class IdentityContext : IDatabaseContext
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
dataSourceBuilder.UseNodaTime(); dataSourceBuilder.UseNodaTime();
_dataSource = dataSourceBuilder.Build(); _dataSource = dataSourceBuilder.Build();
_loggerFactory = loggerFactory;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder => optionsBuilder
.UseNpgsql(_dataSource, o => o.UseNodaTime()) .UseNpgsql(_dataSource, o => o.UseNodaTime())
.UseSnakeCaseNamingConvention(); .UseSnakeCaseNamingConvention()
.UseLoggerFactory(_loggerFactory);
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{ {
@ -67,6 +71,6 @@ public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<Iden
// Get the configuration as our config class // Get the configuration as our config class
.Get<InstanceConfig>() ?? new(); .Get<InstanceConfig>() ?? new();
return new IdentityContext(config); return new IdentityContext(config, null);
} }
} }

View file

@ -1,6 +1,7 @@
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using Serilog; using Serilog;
using Foxchat.Core; using Foxchat.Core;
using Foxchat.Core.Extensions;
using Foxchat.Identity; using Foxchat.Identity;
using Foxchat.Identity.Database; using Foxchat.Identity.Database;
using Foxchat.Identity.Services; using Foxchat.Identity.Services;
@ -11,7 +12,7 @@ var builder = WebApplication.CreateBuilder(args);
var config = builder.AddConfiguration<InstanceConfig>("identity.ini"); var config = builder.AddConfiguration<InstanceConfig>("identity.ini");
builder.AddSerilog(config.LogEventLevel); builder.AddSerilog();
await BuildInfo.ReadBuildInfo(); await BuildInfo.ReadBuildInfo();
Log.Information("Starting Foxchat.Identity {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash); Log.Information("Starting Foxchat.Identity {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);

View file

@ -2,11 +2,6 @@ Host = localhost
Port = 7611 Port = 7611
Domain = id.fox.localhost Domain = id.fox.localhost
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
LogEventLevel = Debug
; Optional logging to Seq
SeqLogUrl = http://localhost:5341
[Database] [Database]
; The database URL in ADO.NET format. ; The database URL in ADO.NET format.
Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=password" Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=password"
@ -15,3 +10,11 @@ Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=passwo
Timeout = 5 Timeout = 5
; The maximum number of open connections. Defaults to 50. ; The maximum number of open connections. Defaults to 50.
MaxPoolSize = 500 MaxPoolSize = 500
[Logging]
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
LogEventLevel = Debug
; Whether to log SQL queries.
LogQueries = true
; Optional logging to Seq
SeqLogUrl = http://localhost:5341