2020-01-26 00:27:45 +00:00
|
|
|
using System;
|
2020-04-28 22:25:31 +00:00
|
|
|
using System.Globalization;
|
2020-01-26 00:27:45 +00:00
|
|
|
|
|
|
|
using App.Metrics;
|
|
|
|
|
|
|
|
using Autofac;
|
2020-02-01 13:40:57 +00:00
|
|
|
using Autofac.Extensions.DependencyInjection;
|
2020-01-26 00:27:45 +00:00
|
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2020-02-01 13:40:57 +00:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2020-01-26 00:27:45 +00:00
|
|
|
|
|
|
|
using NodaTime;
|
|
|
|
|
|
|
|
using Serilog;
|
|
|
|
using Serilog.Events;
|
|
|
|
using Serilog.Formatting.Compact;
|
|
|
|
using Serilog.Sinks.SystemConsole.Themes;
|
|
|
|
|
|
|
|
namespace PluralKit.Core
|
|
|
|
{
|
|
|
|
public class DataStoreModule: Module
|
|
|
|
{
|
|
|
|
protected override void Load(ContainerBuilder builder)
|
|
|
|
{
|
|
|
|
builder.RegisterType<DbConnectionCountHolder>().SingleInstance();
|
|
|
|
builder.RegisterType<DbConnectionFactory>().AsSelf().SingleInstance();
|
|
|
|
builder.RegisterType<PostgresDataStore>().AsSelf().As<IDataStore>();
|
2020-06-12 18:29:50 +00:00
|
|
|
builder.RegisterType<Schemas>().AsSelf();
|
2020-02-01 13:40:57 +00:00
|
|
|
|
|
|
|
builder.Populate(new ServiceCollection().AddMemoryCache());
|
2020-01-26 00:27:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class ConfigModule<T>: Module where T: new()
|
|
|
|
{
|
|
|
|
private string _submodule;
|
|
|
|
|
|
|
|
public ConfigModule(string submodule = null)
|
|
|
|
{
|
|
|
|
_submodule = submodule;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Load(ContainerBuilder builder)
|
|
|
|
{
|
|
|
|
// We're assuming IConfiguration is already available somehow - it comes from various places (auto-injected in ASP, etc)
|
|
|
|
|
|
|
|
// Register the CoreConfig and where to find it
|
|
|
|
builder.Register(c => c.Resolve<IConfiguration>().GetSection("PluralKit").Get<CoreConfig>() ?? new CoreConfig()).SingleInstance();
|
|
|
|
|
|
|
|
// Register the submodule config (BotConfig, etc) if specified
|
|
|
|
if (_submodule != null)
|
|
|
|
builder.Register(c => c.Resolve<IConfiguration>().GetSection("PluralKit").GetSection(_submodule).Get<T>() ?? new T()).SingleInstance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class MetricsModule: Module
|
|
|
|
{
|
|
|
|
private readonly string _onlyContext;
|
|
|
|
|
|
|
|
public MetricsModule(string onlyContext = null)
|
|
|
|
{
|
|
|
|
_onlyContext = onlyContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Load(ContainerBuilder builder)
|
|
|
|
{
|
|
|
|
builder.Register(c => InitMetrics(c.Resolve<CoreConfig>()))
|
2020-02-01 11:33:43 +00:00
|
|
|
.AsSelf().As<IMetrics>().SingleInstance();
|
2020-01-26 00:27:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private IMetricsRoot InitMetrics(CoreConfig config)
|
|
|
|
{
|
|
|
|
var builder = AppMetrics.CreateDefaultBuilder();
|
|
|
|
if (config.InfluxUrl != null && config.InfluxDb != null)
|
|
|
|
builder.Report.ToInfluxDb(config.InfluxUrl, config.InfluxDb);
|
|
|
|
if (_onlyContext != null)
|
|
|
|
builder.Filter.ByIncludingOnlyContext(_onlyContext);
|
|
|
|
return builder.Build();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class LoggingModule: Module
|
|
|
|
{
|
|
|
|
private readonly string _component;
|
|
|
|
|
|
|
|
public LoggingModule(string component)
|
|
|
|
{
|
|
|
|
_component = component;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Load(ContainerBuilder builder)
|
|
|
|
{
|
|
|
|
builder.Register(c => InitLogger(c.Resolve<CoreConfig>())).AsSelf().SingleInstance();
|
|
|
|
}
|
|
|
|
|
|
|
|
private ILogger InitLogger(CoreConfig config)
|
|
|
|
{
|
2020-04-28 22:25:31 +00:00
|
|
|
var outputTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.ffffff}] {Level:u3} {Message:lj}{NewLine}{Exception}";
|
|
|
|
|
2020-01-26 00:27:45 +00:00
|
|
|
return new LoggerConfiguration()
|
|
|
|
.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
|
|
|
|
.MinimumLevel.Debug()
|
|
|
|
.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,
|
|
|
|
rollingInterval: RollingInterval.Day,
|
2020-05-31 01:22:41 +00:00
|
|
|
flushToDiskInterval: TimeSpan.FromMilliseconds(50),
|
2020-04-28 22:25:31 +00:00
|
|
|
restrictedToMinimumLevel: LogEventLevel.Information,
|
|
|
|
formatProvider: new UTCTimestampFormatProvider(),
|
|
|
|
buffered: true);
|
|
|
|
|
2020-01-26 00:27:45 +00:00
|
|
|
a.File(
|
|
|
|
new RenderedCompactJsonFormatter(),
|
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),
|
2020-01-26 00:27:45 +00:00
|
|
|
restrictedToMinimumLevel: LogEventLevel.Information,
|
2020-04-28 22:25:31 +00:00
|
|
|
buffered: true);
|
|
|
|
})
|
|
|
|
// TODO: render as UTC in the console, too? or just in log files
|
2020-01-26 00:27:45 +00:00
|
|
|
.WriteTo.Async(a =>
|
2020-04-28 22:25:31 +00:00
|
|
|
a.Console(theme: AnsiConsoleTheme.Code, outputTemplate: outputTemplate, formatProvider: new UTCTimestampFormatProvider()))
|
2020-01-26 00:27:45 +00:00
|
|
|
.CreateLogger();
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|