Files
carl bystrom d2fb16f34f Horde: Replace use of OpenTracing with OpenTelemetry in server
#preflight 64514b156538e45f755a9223

[CL 25304570 by carl bystrom in ue5-main branch]
2023-05-02 13:46:14 -04:00

214 lines
7.2 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using EpicGames.Core;
using Horde.Server.Commands;
using Horde.Server.Utilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Configuration;
using Serilog.Exceptions;
using Serilog.Exceptions.Core;
using Serilog.Exceptions.Grpc.Destructurers;
using Serilog.Formatting.Json;
using Serilog.Sinks.SystemConsole.Themes;
namespace Horde.Server
{
static class LoggerExtensions
{
public static LoggerConfiguration Console(this LoggerSinkConfiguration sinkConfig, ServerSettings settings)
{
if (settings.LogJsonToStdOut)
{
return sinkConfig.Console(new JsonFormatter(renderMessage: true));
}
else
{
ConsoleTheme theme;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version < new Version(10, 0))
{
theme = SystemConsoleTheme.Literate;
}
else
{
theme = AnsiConsoleTheme.Code;
}
return sinkConfig.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:w3}] {Indent}{Message:l}{NewLine}{Exception}", theme: theme, restrictedToMinimumLevel: settings.ConsoleLogLevel);
}
}
public static LoggerConfiguration WithHordeConfig(this LoggerConfiguration configuration, ServerSettings settings)
{
if (settings.OpenTelemetry.EnableDatadogCompatibility)
{
configuration = configuration.Enrich.With<OpenTelemetryDatadogLogEnricher>();
}
return configuration;
}
}
class Program
{
public static SemVer Version => s_version;
public static string DeploymentEnvironment { get; } = GetEnvironment();
public static DirectoryReference AppDir { get; } = GetAppDir();
public static DirectoryReference DataDir { get; } = GetDataDir();
public static FileReference UserConfigFile { get; } = FileReference.Combine(DataDir, "Horde.json");
public static Type[] ConfigSchemas = FindSchemaTypes();
static SemVer s_version;
static Type[] FindSchemaTypes()
{
List<Type> schemaTypes = new List<Type>();
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
if (type.GetCustomAttribute<JsonSchemaAttribute>() != null)
{
schemaTypes.Add(type);
}
}
return schemaTypes.ToArray();
}
public static async Task<int> Main(string[] args)
{
FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
if (String.IsNullOrEmpty(versionInfo.ProductVersion))
{
s_version = SemVer.Parse("0.0.0");
}
else
{
s_version = SemVer.Parse(versionInfo.ProductVersion);
}
CommandLineArguments arguments = new CommandLineArguments(args);
IConfiguration config = CreateConfig(UserConfigFile);
ServerSettings hordeSettings = new ServerSettings();
config.GetSection("Horde").Bind(hordeSettings);
DirectoryReference logDir = AppDir;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
logDir = DirectoryReference.Combine(DataDir);
}
Serilog.Log.Logger = new LoggerConfiguration()
.WithHordeConfig(hordeSettings)
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
.WithDefaultDestructurers()
.WithDestructurers(new[] { new RpcExceptionDestructurer() }))
.WriteTo.Console(hordeSettings)
.WriteTo.File(Path.Combine(logDir.FullName, "Log.txt"), outputTemplate: "[{Timestamp:HH:mm:ss} {Level:w3}] {Indent}{Message:l}{NewLine}{Exception} [{SourceContext}]", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 20 * 1024 * 1024, retainedFileCountLimit: 10)
.WriteTo.File(new JsonFormatter(renderMessage: true), Path.Combine(logDir.FullName, "Log.json"), rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 20 * 1024 * 1024, retainedFileCountLimit: 10)
.ReadFrom.Configuration(config)
.CreateLogger();
Serilog.Log.Logger.Information("Server version: {Version}", Version);
ServiceCollection services = new ServiceCollection();
services.AddCommandsFromAssembly(Assembly.GetExecutingAssembly());
services.AddLogging(builder => builder.AddSerilog());
services.AddSingleton<IConfiguration>(config);
services.AddSingleton<ServerSettings>(hordeSettings);
#pragma warning disable ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'
await using ServiceProvider serviceProvider = services.BuildServiceProvider();
return await CommandHost.RunAsync(arguments, serviceProvider, typeof(ServerCommand));
#pragma warning restore ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'
}
// Used by WebApplicationFactory in controller tests. Uses reflection to call this exact function signature.
public static IHostBuilder CreateHostBuilder(string[] args) => ServerCommand.CreateHostBuilderForTesting(args);
/// <summary>
/// Gets the current environment
/// </summary>
/// <returns></returns>
static string GetEnvironment()
{
string? environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (String.IsNullOrEmpty(environment))
{
environment = "Production";
}
return environment;
}
/// <summary>
/// Get the application directory
/// </summary>
/// <returns></returns>
static DirectoryReference GetAppDir()
{
return new FileReference(Assembly.GetExecutingAssembly().Location).Directory;
}
/// <summary>
/// Gets the default directory for storing application data
/// </summary>
/// <returns>The default data directory</returns>
static DirectoryReference GetDataDir()
{
IConfiguration config = CreateConfig(null);
string? dataDir = config.GetSection("Horde").GetValue(typeof(string), nameof(ServerSettings.DataDir)) as string;
if (dataDir != null)
{
return DirectoryReference.Combine(GetAppDir(), dataDir);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
DirectoryReference? dir = DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.CommonApplicationData);
if (dir != null)
{
return DirectoryReference.Combine(dir, "HordeServer");
}
}
return DirectoryReference.Combine(GetAppDir(), "Data");
}
/// <summary>
/// Constructs a configuration object for the current environment
/// </summary>
/// <param name="userConfigFile"></param>
/// <returns></returns>
static IConfiguration CreateConfig(FileReference? userConfigFile)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(AppDir.FullName)
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Build.json", optional: true) // specific settings for builds (installer/dockerfile)
.AddJsonFile($"appsettings.{DeploymentEnvironment}.json", optional: true) // environment variable overrides, also used in k8s setups with Helm
.AddJsonFile("appsettings.User.json", optional: true);
if (userConfigFile != null)
{
builder = builder.AddJsonFile(userConfigFile.FullName, optional: true, reloadOnChange: true);
}
return builder.AddEnvironmentVariables().Build();
}
}
}