PluralKit/PluralKit.Core/Modules/LoggingModule.cs

133 lines
5.6 KiB
C#
Raw Normal View History

2021-08-27 15:03:47 +00:00
using System;
2020-04-28 22:25:31 +00:00
using System.Globalization;
2020-01-26 00:27:45 +00:00
using Autofac;
using NodaTime;
using Serilog;
using Serilog.Events;
2020-01-26 00:27:45 +00:00
using Serilog.Formatting.Compact;
using Serilog.Sinks.Elasticsearch;
2020-01-26 00:27:45 +00:00
using Serilog.Sinks.SystemConsole.Themes;
namespace PluralKit.Core
{
public class LoggingModule: Module
{
private readonly string _component;
2020-08-27 16:20:20 +00:00
private readonly Action<LoggerConfiguration> _fn;
2020-01-26 00:27:45 +00:00
2020-08-27 16:20:20 +00:00
public LoggingModule(string component, Action<LoggerConfiguration> fn = null)
2020-01-26 00:27:45 +00:00
{
_component = component;
2020-08-27 16:20:20 +00:00
_fn = fn ?? (_ => { });
2020-01-26 00:27:45 +00:00
}
protected override void Load(ContainerBuilder builder)
{
2020-08-27 21:36:02 +00:00
builder
.Register(c => InitLogger(c.Resolve<CoreConfig>()))
.AsSelf()
.SingleInstance()
// AutoActivate ensures logging is enabled as early as possible in the API startup flow
// since we set the Log.Logger global >.>
.AutoActivate();
2020-11-15 12:53:31 +00:00
builder.Register(c => new Microsoft.Extensions.Logging.LoggerFactory().AddSerilog(c.Resolve<ILogger>()))
.As<Microsoft.Extensions.Logging.ILoggerFactory>()
.SingleInstance();
2020-01-26 00:27:45 +00:00
}
private ILogger InitLogger(CoreConfig config)
{
var consoleTemplate = "[{Timestamp:HH:mm:ss.fff}] {Level:u3} {Message:lj}{NewLine}{Exception}";
2020-04-28 22:25:31 +00:00
var outputTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.ffffff}] {Level:u3} {Message:lj}{NewLine}{Exception}";
2021-08-27 15:03:47 +00:00
var logCfg = new LoggerConfiguration()
.Enrich.FromLogContext()
2020-01-26 00:27:45 +00:00
.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
2020-08-27 21:36:02 +00:00
.Enrich.WithProperty("Component", _component)
.MinimumLevel.Is(config.ConsoleLogLevel)
2020-11-15 12:53:31 +00:00
// Don't want App.Metrics/D#+ spam
2020-09-09 20:22:43 +00:00
.MinimumLevel.Override("App.Metrics", LogEventLevel.Information)
2021-08-27 15:03:47 +00:00
// Actual formatting for these is handled in ScalarFormatting
.Destructure.AsScalar<SystemId>()
.Destructure.AsScalar<MemberId>()
.Destructure.AsScalar<GroupId>()
.Destructure.AsScalar<SwitchId>()
.Destructure.ByTransforming<ProxyTag>(t => new { t.Prefix, t.Suffix })
.Destructure.With<PatchObjectDestructuring>()
2021-08-27 15:03:47 +00:00
2020-01-26 00:27:45 +00:00
.WriteTo.Async(a =>
2020-04-28 22:25:31 +00:00
{
// Both the same output, except one is raw compact JSON and one is plain text.
// Output simultaneously. May remove the JSON formatter later, keeping it just in cast.
2020-05-31 01:22:41 +00:00
// Flush interval is 50ms (down from 10s) to make "tail -f" easier. May be too low?
2020-04-28 22:25:31 +00:00
a.File(
2020-05-01 18:13:15 +00:00
(config.LogDir ?? "logs") + $"/pluralkit.{_component}.log",
2020-04-28 22:25:31 +00:00
outputTemplate: outputTemplate,
2021-03-18 19:21:57 +00:00
retainedFileCountLimit: 10,
2020-04-28 22:25:31 +00:00
rollingInterval: RollingInterval.Day,
2021-03-18 19:21:36 +00:00
fileSizeLimitBytes: null,
2020-05-31 01:22:41 +00:00
flushToDiskInterval: TimeSpan.FromMilliseconds(50),
restrictedToMinimumLevel: config.FileLogLevel,
2020-04-28 22:25:31 +00:00
formatProvider: new UTCTimestampFormatProvider(),
buffered: true);
2020-01-26 00:27:45 +00:00
a.File(
new RenderedCompactJsonFormatter(new ScalarFormatting.JsonValue()),
2020-04-28 22:25:31 +00:00
(config.LogDir ?? "logs") + $"/pluralkit.{_component}.json",
2020-01-26 00:27:45 +00:00
rollingInterval: RollingInterval.Day,
2020-05-31 01:22:41 +00:00
flushToDiskInterval: TimeSpan.FromMilliseconds(50),
restrictedToMinimumLevel: config.FileLogLevel,
2020-04-28 22:25:31 +00:00
buffered: true);
})
.WriteTo.Async(a =>
a.Console(
theme: AnsiConsoleTheme.Code,
outputTemplate: consoleTemplate,
restrictedToMinimumLevel: config.ConsoleLogLevel));
if (config.ElasticUrl != null)
{
var elasticConfig = new ElasticsearchSinkOptions(new Uri(config.ElasticUrl))
{
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
2021-06-10 12:21:05 +00:00
MinimumLogEventLevel = config.ElasticLogLevel,
IndexFormat = "pluralkit-logs-{0:yyyy.MM.dd}",
2021-06-10 12:21:05 +00:00
CustomFormatter = new ScalarFormatting.Elasticsearch(),
};
2021-06-10 12:21:05 +00:00
logCfg.WriteTo.Elasticsearch(elasticConfig);
}
_fn.Invoke(logCfg);
return Log.Logger = logCfg.CreateLogger();
2020-01-26 00:27:45 +00:00
}
}
2021-08-27 15:03:47 +00:00
2020-04-28 22:25:31 +00:00
// Serilog why is this necessary for such a simple thing >.>
public class UTCTimestampFormatProvider: IFormatProvider
{
public object GetFormat(Type formatType) => new UTCTimestampFormatter();
}
public class UTCTimestampFormatter: ICustomFormatter
{
public string Format(string format, object arg, IFormatProvider formatProvider)
{
// Convert offset to UTC and then print
// FormatProvider defaults to locale-specific stuff so we force-default to invariant culture
// If we pass the given formatProvider it'll conveniently ignore it, for some reason >.>
if (arg is DateTimeOffset dto)
return dto.ToUniversalTime().ToString(format, CultureInfo.InvariantCulture);
if (arg is IFormattable f)
return f.ToString(format, CultureInfo.InvariantCulture);
return arg.ToString();
}
}
2020-01-26 00:27:45 +00:00
}