diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 69d6fa32e6..3758ec9b34 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Api public static readonly Version Version = AssemblyName.Version.Semver(); /// - /// The being accessed + /// The instance being accessed /// public long? InstanceId { get; set; } @@ -255,7 +255,7 @@ namespace Tgstation.Server.Api /// Set using the . This initially clears /// /// The to set - /// The for the request + /// The instance for the request public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null) { if (headers == null) diff --git a/src/Tgstation.Server.Api/Models/EntityId.cs b/src/Tgstation.Server.Api/Models/EntityId.cs index bf9ce0883f..85d5cd0487 100644 --- a/src/Tgstation.Server.Api/Models/EntityId.cs +++ b/src/Tgstation.Server.Api/Models/EntityId.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Api.Models { /// - /// Common base of s and s. + /// Common base of s, s, and s. /// public class EntityId { diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index d3f1a78115..60856b07ea 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -6,13 +6,8 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about a server instance /// - public class Instance + public class Instance : EntityId { - /// - /// The id of the . Not modifiable - /// - public long Id { get; set; } - /// /// The name of the /// diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index b9443b8897..69d7d0f79a 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -138,7 +138,7 @@ namespace Tgstation.Server.Client /// The route to run /// The body of the request /// The method of the request - /// The optional for the request + /// The optional instance for the request /// If this is a token refresh operation. /// The for the operation /// A resulting in the response on success diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index ab833b1731..b1c8519892 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -105,7 +105,7 @@ namespace Tgstation.Server.Client /// The type of the response body /// The server route to make the request to /// The request body - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Create(string route, TBody body, long instanceId, CancellationToken cancellationToken); @@ -115,7 +115,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Create(string route, long instanceId, CancellationToken cancellationToken); @@ -125,7 +125,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Patch(string route, long instanceId, CancellationToken cancellationToken); @@ -135,7 +135,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Read(string route, long instanceId, CancellationToken cancellationToken); @@ -147,7 +147,7 @@ namespace Tgstation.Server.Client /// The type of the response body /// The server route to make the request to /// The request body - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); @@ -156,7 +156,7 @@ namespace Tgstation.Server.Client /// Run an HTTP DELETE request /// /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A representing the running operation Task Delete(string route, long instanceId, CancellationToken cancellationToken); @@ -167,7 +167,7 @@ namespace Tgstation.Server.Client /// The type to of the request body /// The server route to make the request to /// The request body - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A representing the running operation Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken); @@ -177,7 +177,7 @@ namespace Tgstation.Server.Client /// /// The type of the response body /// The server route to make the request to - /// The to make the request to + /// The instance to make the request to /// The for the operation /// A resulting in the response body as a Task Delete(string route, long instanceId, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index 73d60a3893..57030b1deb 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Client /// /// Create or attach an /// - /// The to create. will be ignored + /// The to create. will be ignored /// The for the operation /// A resulting in the created or attached Task CreateOrAttach(Instance instance, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index dd4caa7835..8a6a3c0071 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Globalization; @@ -111,6 +112,11 @@ namespace Tgstation.Server.Host.Components.Chat /// ulong channelIdCounter; + /// + /// The number of s processed. + /// + long messagesProcessed; + /// /// If has been called /// @@ -407,7 +413,9 @@ namespace Tgstation.Server.Host.Components.Chat foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); - await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); + var messageNumber = Interlocked.Increment(ref messagesProcessed); + using (LogContext.PushProperty("ChatMessage", messageNumber)) + await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); messageTasks.Remove(I.Key); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index 24e18962e4..5f661fd4f7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -11,7 +11,7 @@ public string Content { get; set; } /// - /// The who sent the + /// The who sent the /// public ChatUser User { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index ee975d13a8..475c625482 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : ILatestCompileJobProvider, IHostedService, IDisposable + public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IDisposable { /// /// The for the @@ -45,12 +45,6 @@ namespace Tgstation.Server.Host.Components /// IConfiguration Configuration { get; } - /// - /// Rename the - /// - /// The new name for the - void Rename(string newName); - /// /// Change the for the /// diff --git a/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs new file mode 100644 index 0000000000..d40afc0023 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IRenameNotifyee.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Handler for an instance being renamed. + /// + public interface IRenameNotifyee + { + /// + /// Called when the owning is renamed. + /// + /// The new . + /// The for the operation. + /// A representing the running operation. + Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 76d67ce595..62d5545faa 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -134,12 +135,15 @@ namespace Tgstation.Server.Host.Components /// public void Dispose() { - timerCts?.Dispose(); - Configuration.Dispose(); - Chat.Dispose(); - Watchdog.Dispose(); - dmbFactory.Dispose(); - RepositoryManager.Dispose(); + using (LogContext.PushProperty("Instance", metadata.Id)) + { + timerCts?.Dispose(); + Configuration.Dispose(); + Chat.Dispose(); + Watchdog.Dispose(); + dmbFactory.Dispose(); + RepositoryManager.Dispose(); + } } /// @@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components #pragma warning disable CA1502 // TODO: Decomplexify async Task TimerLoop(uint minutes, CancellationToken cancellationToken) { - logger.LogTrace("Entering auto-update loop"); + logger.LogDebug("Entering auto-update loop"); while (true) try { @@ -393,17 +397,20 @@ namespace Tgstation.Server.Host.Components #pragma warning restore CA1502 /// - public void Rename(string newName) + public Task InstanceRenamed(string newName, CancellationToken cancellationToken) { if (String.IsNullOrWhiteSpace(newName)) throw new ArgumentNullException(nameof(newName)); metadata.Name = newName; + return Watchdog.InstanceRenamed(newName, cancellationToken); } /// public async Task StartAsync(CancellationToken cancellationToken) { - await Task.WhenAll( + using (LogContext.PushProperty("Instance", metadata.Id)) + { + await Task.WhenAll( SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), @@ -411,23 +418,27 @@ namespace Tgstation.Server.Host.Components dmbFactory.StartAsync(cancellationToken)) .ConfigureAwait(false); - // dependent on so many things, its just safer this way - await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false); + // dependent on so many things, its just safer this way + await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false); - await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false); + await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false); + } } /// public async Task StopAsync(CancellationToken cancellationToken) { - await SetAutoUpdateInterval(0).ConfigureAwait(false); - await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false); - await Task.WhenAll( - Configuration.StopAsync(cancellationToken), - ByondManager.StopAsync(cancellationToken), - Chat.StopAsync(cancellationToken), - dmbFactory.StopAsync(cancellationToken)) - .ConfigureAwait(false); + using (LogContext.PushProperty("Instance", metadata.Id)) + { + await SetAutoUpdateInterval(0).ConfigureAwait(false); + await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false); + await Task.WhenAll( + Configuration.StopAsync(cancellationToken), + ByondManager.StopAsync(cancellationToken), + Chat.StopAsync(cancellationToken), + dmbFactory.StopAsync(cancellationToken)) + .ConfigureAwait(false); + } } /// @@ -438,8 +449,12 @@ namespace Tgstation.Server.Host.Components { if (timerTask != null) { + logger.LogTrace("Cancelling auto-update task"); timerCts.Cancel(); + timerCts.Dispose(); toWait = timerTask; + timerTask = null; + timerCts = null; } else toWait = Task.CompletedTask; @@ -447,13 +462,20 @@ namespace Tgstation.Server.Host.Components await toWait.ConfigureAwait(false); if (newInterval == 0) + { + logger.LogTrace("New auto-update interval is 0. Not starting task."); return; + } + lock (timerLock) { // race condition, just quit if (timerTask != null) + { + logger.LogDebug("Aborting auto update interval change due to race condition!"); return; - timerCts?.Dispose(); + } + timerCts = new CancellationTokenSource(); timerTask = TimerLoop(newInterval, timerCts.Token); } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 062dca8163..e0045ccbc9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Configuration; @@ -72,7 +73,7 @@ namespace Tgstation.Server.Host.Components readonly ILogger logger; /// - /// Map of s to respective s. Also used as a . + /// Map of instance s to respective s. Also used as a . /// readonly IDictionary instances; diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs index 5ca5347e3d..5d262a277e 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeHandler.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components.Interop.Bridge +namespace Tgstation.Server.Host.Components.Interop.Bridge { /// interface IBridgeHandler : IBridgeDispatcher @@ -10,13 +7,5 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge /// The for the . /// DMApiParameters DMApiParameters { get; } - - /// - /// Called when the owning is renamed. - /// - /// The new . - /// The for the operation. - /// A representing the running operation. - Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs index a49c9f7738..2f5ff7f5c4 100644 --- a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs @@ -120,5 +120,8 @@ namespace Tgstation.Server.Host.Components.Session /// public void Resume() => throw new NotSupportedException(); + + /// + public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index c598d9a331..fdc9536ec5 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Session /// /// Handles communication with a DreamDaemon /// - interface ISessionController : IProcessBase + interface ISessionController : IRenameNotifyee, IProcessBase { /// /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 3d1590cc4e..dca20a3a14 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -1,6 +1,7 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Globalization; @@ -105,6 +106,11 @@ namespace Tgstation.Server.Host.Components.Session /// readonly ReattachInformation reattachInformation; + /// + /// The metadata. + /// + readonly Api.Models.Instance metadata; + /// /// A used for the topic send operation made on reattaching. /// @@ -194,6 +200,7 @@ namespace Tgstation.Server.Host.Components.Session /// Construct a /// /// The value of + /// The owning . /// The value of /// The value of /// The value of @@ -206,6 +213,7 @@ namespace Tgstation.Server.Host.Components.Session /// If this is a reattached session. public SessionController( ReattachInformation reattachInformation, + Api.Models.Instance metadata, IProcess process, IByondExecutableLock byondLock, ITopicClient byondTopicSender, @@ -218,6 +226,7 @@ namespace Tgstation.Server.Host.Components.Session bool reattached) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); + this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); this.process = process ?? throw new ArgumentNullException(nameof(process)); this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); @@ -364,141 +373,145 @@ namespace Tgstation.Server.Host.Components.Session if (parameters == null) throw new ArgumentNullException(nameof(parameters)); - var response = new BridgeResponse(); - switch (parameters.CommandType) + using (LogContext.PushProperty("Instance", metadata.Id)) { - case BridgeCommandType.ChatSend: - if (parameters.ChatMessage == null) - return new BridgeResponse - { - ErrorMessage = "Missing chatMessage field!" - }; - - if (parameters.ChatMessage.ChannelIds == null) - return new BridgeResponse - { - ErrorMessage = "Missing channelIds field in chatMessage!" - }; - - if(parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _))) - return new BridgeResponse - { - ErrorMessage = "Invalid channelIds in chatMessage!" - }; - - if (parameters.ChatMessage.Text == null) - return new BridgeResponse - { - ErrorMessage = "Missing message field in chatMessage!" - }; - - await chat.SendMessage( - parameters.ChatMessage.Text, - parameters.ChatMessage.ChannelIds.Select(UInt64.Parse), - cancellationToken).ConfigureAwait(false); - break; - case BridgeCommandType.Prime: - var oldPrimeTcs = primeTcs; - primeTcs = new TaskCompletionSource(); - oldPrimeTcs.SetResult(null); - break; - case BridgeCommandType.Kill: - logger.LogInformation("Bridge requested process termination!"); - TerminationWasRequested = true; - process.Terminate(); - break; - case BridgeCommandType.PortUpdate: - lock (synchronizationLock) - { - if (!parameters.CurrentPort.HasValue) - { - /////UHHHH - logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); + logger.LogTrace("Handling bridge request..."); + var response = new BridgeResponse(); + switch (parameters.CommandType) + { + case BridgeCommandType.ChatSend: + if (parameters.ChatMessage == null) return new BridgeResponse { - ErrorMessage = "Missing stringified port as data parameter!" + ErrorMessage = "Missing chatMessage field!" }; + + if (parameters.ChatMessage.ChannelIds == null) + return new BridgeResponse + { + ErrorMessage = "Missing channelIds field in chatMessage!" + }; + + if (parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _))) + return new BridgeResponse + { + ErrorMessage = "Invalid channelIds in chatMessage!" + }; + + if (parameters.ChatMessage.Text == null) + return new BridgeResponse + { + ErrorMessage = "Missing message field in chatMessage!" + }; + + await chat.SendMessage( + parameters.ChatMessage.Text, + parameters.ChatMessage.ChannelIds.Select(UInt64.Parse), + cancellationToken).ConfigureAwait(false); + break; + case BridgeCommandType.Prime: + var oldPrimeTcs = primeTcs; + primeTcs = new TaskCompletionSource(); + oldPrimeTcs.SetResult(null); + break; + case BridgeCommandType.Kill: + logger.LogInformation("Bridge requested process termination!"); + TerminationWasRequested = true; + process.Terminate(); + break; + case BridgeCommandType.PortUpdate: + lock (synchronizationLock) + { + if (!parameters.CurrentPort.HasValue) + { + /////UHHHH + logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); + return new BridgeResponse + { + ErrorMessage = "Missing stringified port as data parameter!" + }; + } + + var currentPort = parameters.CurrentPort.Value; + if (!nextPort.HasValue) + reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to + else + { + // nextPort is ready, tell DD to switch to that + // if it fails it'll kill itself + response.NewPort = nextPort.Value; + reattachInformation.Port = nextPort.Value; + nextPort = null; + + // we'll also get here from SetPort so complete that task + var tmpTcs = portAssignmentTcs; + portAssignmentTcs = null; + tmpTcs.SetResult(true); + } + + portClosedForReboot = false; } - var currentPort = parameters.CurrentPort.Value; - if (!nextPort.HasValue) - reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to - else - { - // nextPort is ready, tell DD to switch to that - // if it fails it'll kill itself - response.NewPort = nextPort.Value; - reattachInformation.Port = nextPort.Value; - nextPort = null; + break; + case BridgeCommandType.Startup: + apiValidationStatus = ApiValidationStatus.BadValidationRequest; + if (parameters.Version == null) + return new BridgeResponse + { + ErrorMessage = "Missing dmApiVersion field!" + }; - // we'll also get here from SetPort so complete that task - var tmpTcs = portAssignmentTcs; - portAssignmentTcs = null; - tmpTcs.SetResult(true); + DMApiVersion = parameters.Version; + switch (parameters.MinimumSecurityLevel) + { + case DreamDaemonSecurity.Ultrasafe: + apiValidationStatus = ApiValidationStatus.RequiresUltrasafe; + break; + case DreamDaemonSecurity.Safe: + apiValidationStatus = ApiValidationStatus.RequiresSafe; + break; + case DreamDaemonSecurity.Trusted: + apiValidationStatus = ApiValidationStatus.RequiresTrusted; + break; + case null: + return new BridgeResponse + { + ErrorMessage = "Missing minimumSecurityLevel field!" + }; + default: + return new BridgeResponse + { + ErrorMessage = "Invalid minimumSecurityLevel!" + }; } - portClosedForReboot = false; - } + response.RuntimeInformation = reattachInformation.RuntimeInformation; - break; - case BridgeCommandType.Startup: - apiValidationStatus = ApiValidationStatus.BadValidationRequest; - if (parameters.Version == null) - return new BridgeResponse + // Load custom commands + chatTrackingContext.CustomCommands = parameters.CustomCommands; + break; + case BridgeCommandType.Reboot: + if (ClosePortOnReboot) { - ErrorMessage = "Missing dmApiVersion field!" - }; + chatTrackingContext.Active = false; + response.NewPort = 0; + portClosedForReboot = true; + } - DMApiVersion = parameters.Version; - switch (parameters.MinimumSecurityLevel) - { - case DreamDaemonSecurity.Ultrasafe: - apiValidationStatus = ApiValidationStatus.RequiresUltrasafe; - break; - case DreamDaemonSecurity.Safe: - apiValidationStatus = ApiValidationStatus.RequiresSafe; - break; - case DreamDaemonSecurity.Trusted: - apiValidationStatus = ApiValidationStatus.RequiresTrusted; - break; - case null: - return new BridgeResponse - { - ErrorMessage = "Missing minimumSecurityLevel field!" - }; - default: - return new BridgeResponse - { - ErrorMessage = "Invalid minimumSecurityLevel!" - }; - } + var oldRebootTcs = rebootTcs; + rebootTcs = new TaskCompletionSource(); + oldRebootTcs.SetResult(null); + break; + case null: + response.ErrorMessage = "Missing commandType!"; + break; + default: + response.ErrorMessage = "Requested commandType not supported!"; + break; + } - response.RuntimeInformation = reattachInformation.RuntimeInformation; - - // Load custom commands - chatTrackingContext.CustomCommands = parameters.CustomCommands; - break; - case BridgeCommandType.Reboot: - if (ClosePortOnReboot) - { - chatTrackingContext.Active = false; - response.NewPort = 0; - portClosedForReboot = true; - } - - var oldRebootTcs = rebootTcs; - rebootTcs = new TaskCompletionSource(); - oldRebootTcs.SetResult(null); - break; - case null: - response.ErrorMessage = "Missing commandType!"; - break; - default: - response.ErrorMessage = "Requested commandType not supported!"; - break; + return response; } - - return response; } /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 9be2d437be..b1861108dc 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -123,7 +123,7 @@ namespace Tgstation.Server.Host.Components.Session try { - socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + socket.Bind(new IPEndPoint(IPAddress.Any, port)); } catch (Exception ex) { @@ -326,6 +326,7 @@ namespace Tgstation.Server.Host.Components.Session var sessionController = new SessionController( reattachInformation, + instance, process, byondLock, byondTopicSender, @@ -391,6 +392,7 @@ namespace Tgstation.Server.Host.Components.Session var controller = new SessionController( reattachInformation, + instance, process, byondLock, byondTopicSender, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 1722376728..96fbbbdf30 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -346,5 +346,9 @@ namespace Tgstation.Server.Host.Components.Watchdog await LaunchNoLock(true, false, null, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); } + + /// + public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) + => Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs index 07c73f7376..a399db4e8d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs @@ -583,5 +583,11 @@ namespace Tgstation.Server.Host.Components.Watchdog Alpha = alphaServer?.Release(), Bravo = bravoServer?.Release() }; + + /// + public override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) + => Task.WhenAll( + alphaServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask, + bravoServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 536096c499..6c40b1bd97 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Runs and monitors the twin server controllers /// - public interface IWatchdog : IHostedService, IDisposable, IEventConsumer + public interface IWatchdog : IHostedService, IDisposable, IEventConsumer, IRenameNotifyee { /// /// If the watchdog is running diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d2400a8170..6f2b0341d1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -289,10 +290,10 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (++heartbeatsMissed) { case 1: - Logger.LogDebug("DEFCON 4: Watchdog missed first heartbeat!"); + Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!"); break; case 2: - var message2 = "DEFCON 3: Watchdog has missed 2 heartbeats!"; + var message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!"; Logger.LogInformation(message2); await Chat.SendWatchdogMessage(message2, true, cancellationToken).ConfigureAwait(false); break; @@ -300,7 +301,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var actionToTake = shouldShutdown ? "shutdown" : "be restarted"; - var message3 = $"DEFCON 2: Watchdog has missed 3 heartbeats! If DreamDaemon does not respond to the next one, the watchdog will {actionToTake}!"; + var message3 = $"DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!"; Logger.LogWarning(message3); await Chat.SendWatchdogMessage(message3, false, cancellationToken).ConfigureAwait(false); break; @@ -565,137 +566,138 @@ namespace Tgstation.Server.Host.Components.Watchdog try { for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration) - try - { - Logger.LogDebug("Iteration {0} of monitor loop", iteration); - - // load the activation tasks into local variables - var serverTasks = GetMonitoredServerTasks(monitorState); - if (serverTasks.Count != 5) - throw new InvalidOperationException("Expected 5 monitored server tasks!"); - - var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed]; - var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted]; - var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed]; - var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted]; - var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete]; - - Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; - var newDmbAvailable = DmbFactory.OnNewerDmb; - - var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value; - var heartbeat = heartbeatSeconds == 0 - ? Extensions.TaskExtensions.InfiniteTask() - : Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds)); - - // cancel waiting if requested - var cancelTcs = new TaskCompletionSource(); - var toWaitOn = Task.WhenAny( - activeServerLifetime, - activeServerReboot, - inactiveServerLifetime, - inactiveServerReboot, - inactiveStartupComplete, - heartbeat, - newDmbAvailable, - cancelTcs.Task, - activeLaunchParametersChanged); - - // wait for something to happen - using (cancellationToken.Register(() => cancelTcs.SetCanceled())) - await toWaitOn.ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - Logger.LogTrace("Monitor activated"); - - // always run HandleMonitorWakeup from the context of the semaphore lock - using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) + using (LogContext.PushProperty("Monitor", iteration)) + try { - // multiple things may have happened, handle them one at a time - for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) - { - MonitorActivationReason activationReason = default; // this will always be assigned before being used + Logger.LogTrace("Iteration {0} of monitor loop", iteration); - bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) + // load the activation tasks into local variables + var serverTasks = GetMonitoredServerTasks(monitorState); + if (serverTasks.Count != 5) + throw new InvalidOperationException("Expected 5 monitored server tasks!"); + + var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed]; + var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted]; + var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed]; + var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted]; + var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete]; + + Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; + var newDmbAvailable = DmbFactory.OnNewerDmb; + + var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value; + var heartbeat = heartbeatSeconds == 0 + ? Extensions.TaskExtensions.InfiniteTask() + : Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds)); + + // cancel waiting if requested + var cancelTcs = new TaskCompletionSource(); + var toWaitOn = Task.WhenAny( + activeServerLifetime, + activeServerReboot, + inactiveServerLifetime, + inactiveServerReboot, + inactiveStartupComplete, + heartbeat, + newDmbAvailable, + cancelTcs.Task, + activeLaunchParametersChanged); + + // wait for something to happen + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await toWaitOn.ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + Logger.LogTrace("Monitor activated"); + + // always run HandleMonitorWakeup from the context of the semaphore lock + using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) + { + // multiple things may have happened, handle them one at a time + for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) { - var taskCompleted = task?.IsCompleted == true; - task = null; - if (monitorState.NextAction == MonitorAction.Skip) - monitorState.NextAction = MonitorAction.Continue; - else if (taskCompleted) + MonitorActivationReason activationReason = default; // this will always be assigned before being used + + bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) { - activationReason = testActivationReason; - return true; + var taskCompleted = task?.IsCompleted == true; + task = null; + if (monitorState.NextAction == MonitorAction.Skip) + monitorState.NextAction = MonitorAction.Continue; + else if (taskCompleted) + { + activationReason = testActivationReason; + return true; + } + + return false; } - return false; - } + // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState + var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) + || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) + || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) + || CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed) + || CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted) + || CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete) + || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated) + || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat); - // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState - var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) - || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) - || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) - || CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed) - || CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted) - || CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete) - || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated) - || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat); - - if (!anyActivation) - moreActivationsToProcess = false; - else - { - Logger.LogTrace("Reason: {0}", activationReason); - if (activationReason == MonitorActivationReason.Heartbeat) - monitorState.NextAction = await HandleHeartbeat( - monitorState.ActiveServer, - cancellationToken) - .ConfigureAwait(false); + if (!anyActivation) + moreActivationsToProcess = false; else - await HandleMonitorWakeup( - activationReason, - monitorState, - cancellationToken) - .ConfigureAwait(false); + { + Logger.LogTrace("Reason: {0}", activationReason); + if (activationReason == MonitorActivationReason.Heartbeat) + monitorState.NextAction = await HandleHeartbeat( + monitorState.ActiveServer, + cancellationToken) + .ConfigureAwait(false); + else + await HandleMonitorWakeup( + activationReason, + monitorState, + cancellationToken) + .ConfigureAwait(false); + } } } + + Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction); + + // Restart if requested + if (monitorState.NextAction == MonitorAction.Restart) + monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // let this bubble, other exceptions caught below + throw; + } + catch (Exception e) + { + // really, this should NEVER happen + Logger.LogError( + "Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}", + iteration, + JsonConvert.SerializeObject(monitorState), + e); - Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction); + var nextActionMessage = monitorState.NextAction != MonitorAction.Exit + ? "Restarting" + : "Shutting down"; + var chatTask = Chat.SendWatchdogMessage( + $"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}", + false, + cancellationToken); - // Restart if requested - if (monitorState.NextAction == MonitorAction.Restart) - monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // let this bubble, other exceptions caught below - throw; - } - catch (Exception e) - { - // really, this should NEVER happen - Logger.LogError( - "Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}", - iteration, - JsonConvert.SerializeObject(monitorState), - e); + if (disposed) + monitorState.NextAction = MonitorAction.Exit; + else if (monitorState.NextAction != MonitorAction.Exit) + monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - var nextActionMessage = monitorState.NextAction != MonitorAction.Exit - ? "Restarting" - : "Shutting down"; - var chatTask = Chat.SendWatchdogMessage( - $"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}", - false, - cancellationToken); - - if (disposed) - monitorState.NextAction = MonitorAction.Exit; - else if (monitorState.NextAction != MonitorAction.Exit) - monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); - - await chatTask.ConfigureAwait(false); - } + await chatTask.ConfigureAwait(false); + } } catch (OperationCanceledException) { @@ -914,5 +916,8 @@ namespace Tgstation.Server.Host.Components.Watchdog if (Running) await Chat.SendWatchdogMessage("Detaching...", false, cancellationToken).ConfigureAwait(false); } + + /// + public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index a60949643f..8c9afde771 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Globalization; using System.Linq; @@ -161,26 +162,26 @@ namespace Tgstation.Server.Host.Controllers ModelState.Clear(); } - if (ApiHeaders != null) - Logger.LogDebug( - "Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", - AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), - ApiHeaders.ApiVersion.Semver(), - ApiHeaders.RawUserAgent, - Request.Method, - Request.Path, - Request.QueryString, - ApiHeaders.InstanceId); - - try + using (ApiHeaders?.InstanceId != null + ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) + : null) + using (AuthenticationContext != null + ? LogContext.PushProperty("User", AuthenticationContext.User.Id) + : null) + using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) { + if (ApiHeaders != null) + Logger.LogDebug( + "Starting API Request: Version: {1}. User-Agent: {2}", + AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), + ApiHeaders.ApiVersion.Semver(), + ApiHeaders.RawUserAgent, + Request.Method, + Request.Path, + Request.QueryString, + ApiHeaders.InstanceId); await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } - catch (OperationCanceledException e) - { - Logger.LogDebug("Request cancelled! Exception: {0}", e); - throw; - } } #pragma warning restore CA1506 } diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs index 9733a7081f..3ee3155cd8 100644 --- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs +++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Serilog.Context; using System; using System.Net; using System.Threading; @@ -18,6 +20,11 @@ namespace Tgstation.Server.Host.Controllers [Produces(ApiHeaders.ApplicationJson)] public class BridgeController : Controller { + /// + /// Static counter for the number of requests processed. + /// + static long requestsProcessed; + /// /// The for the /// @@ -32,11 +39,17 @@ namespace Tgstation.Server.Host.Controllers /// Initializes a new instance of the . /// /// The value of . + /// The of the server. /// The value of . - public BridgeController(IBridgeDispatcher bridgeDispatcher, ILogger logger) + public BridgeController(IBridgeDispatcher bridgeDispatcher, IHostApplicationLifetime applicationLifetime, ILogger logger) { this.bridgeDispatcher = bridgeDispatcher ?? throw new ArgumentNullException(nameof(bridgeDispatcher)); + if (applicationLifetime == null) + throw new ArgumentNullException(nameof(applicationLifetime)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + applicationLifetime.ApplicationStopped.Register(() => requestsProcessed = 0); } /// @@ -52,26 +65,29 @@ namespace Tgstation.Server.Host.Controllers if (!IPAddress.IsLoopback(Request.HttpContext.Connection.RemoteIpAddress)) return NotFound(); - BridgeParameters request; - try + using (LogContext.PushProperty("Bridge", Interlocked.Increment(ref requestsProcessed))) { - request = JsonConvert.DeserializeObject(data, DMApiConstants.SerializerSettings); + BridgeParameters request; + try + { + request = JsonConvert.DeserializeObject(data, DMApiConstants.SerializerSettings); + } + catch + { + logger.LogWarning("Error deserializing bridge request: {0}", data); + return BadRequest(); + } + + logger.LogTrace("Bridge Request: {0}", data); + + var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false); + if (response == null) + Forbid(); + + var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings); + logger.LogTrace("Bridge Response: {0}", responseJson); + return Content(responseJson, ApiHeaders.ApplicationJson); } - catch - { - logger.LogWarning("Error deserializing bridge request: {0}", data); - return BadRequest(); - } - - logger.LogTrace("Bridge Request: {0}", data); - - var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false); - if (response == null) - Forbid(); - - var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings); - logger.LogTrace("Bridge Response: {0}", responseJson); - return Content(responseJson, ApiHeaders.ApplicationJson); } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index fe2dbb1233..30176cc167 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Detach an with the given . /// - /// The to detach. + /// The of the instance to detach. /// The for the operation. /// A resulting in the of the request. /// Instance detatched successfully. @@ -475,7 +475,7 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); if (renamed) - instanceManager.GetInstance(originalModel).Rename(originalModel.Name); + await instanceManager.GetInstance(originalModel).InstanceRenamed(originalModel.Name, cancellationToken).ConfigureAwait(false); var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart; try @@ -582,7 +582,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Get a specific . /// - /// The to retrieve. + /// The instance to retrieve. /// The for the operation. /// A resulting in the of the request. /// Retrieved successfully. diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7f486eeb43..edfddae43f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -133,10 +133,12 @@ namespace Tgstation.Server.Host.Core var logEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.LogLevel); var formatter = new MessageTemplateTextFormatter( - "{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", + "{Timestamp:o} " + + ServiceCollectionExtensions.SerilogContextTemplate + + ": [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null); - logPath = IOManager.ConcatPath(logPath, "tgs-{Date}.log"); + logPath = IOManager.ConcatPath(logPath, "tgs-.log"); var rollingFileConfig = sinkConfig.File( formatter, logPath, @@ -167,6 +169,7 @@ namespace Tgstation.Server.Host.Core }; }); + // WARNING: STATIC CODE // fucking prevents converting 'sub' to M$ bs // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index 2e6a5d1326..8ff326c398 100644 --- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -15,6 +15,11 @@ namespace Tgstation.Server.Host.Extensions /// static class ServiceCollectionExtensions { + /// + /// Common template used for adding our custom log context to serilog. + /// + public const string SerilogContextTemplate = "(Instance:{Instance}|Job:{Job}|Request:{Request}|User:{User}|Monitor:{Monitor}|Bridge:{Bridge}|Chat:{ChatMessage})"; + /// /// Add a standard binding /// @@ -67,11 +72,14 @@ namespace Tgstation.Server.Host.Extensions configurationAction?.Invoke(configuration); configuration + .Enrich.FromLogContext() .WriteTo .Async(sinkConfiguration => { sinkConfiguration.Console( - outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l}{NewLine} {Message:lj}{NewLine}{Exception}"); + outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} " + + SerilogContextTemplate + + "{NewLine} {Message:lj}{NewLine}{Exception}"); sinkConfigurationAction?.Invoke(sinkConfiguration); }); diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs index 5f01fcbf5e..5cfa3d94f9 100644 --- a/src/Tgstation.Server.Host/Jobs/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Collections.Generic; using System.Linq; @@ -77,67 +78,68 @@ namespace Tgstation.Server.Host.Jobs /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { - try - { - void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + using (LogContext.PushProperty("Job", job.Id)) try { - var oldJob = job; - job = new Job { Id = oldJob.Id }; - - await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false); - - logger.LogDebug("Job {0} completed!", job.Id); - } - catch (OperationCanceledException) - { - logger.LogDebug("Job {0} cancelled!", job.Id); - job.Cancelled = true; - } - catch (JobException e) - { - job.ErrorCode = e.ErrorCode; - job.ExceptionDetails = e.Message; - LogRegularException(); - if (e.InnerException != null) - logger.LogDebug( - "Inner exception for job {0}: {1}", - job.Id, - e.InnerException is JobException - ? e.InnerException.Message - : e.InnerException.ToString()); - } - catch (Exception e) - { - job.ExceptionDetails = e.ToString(); - LogRegularException(); - } - - await databaseContextFactory.UseContext(async databaseContext => - { - var attachedJob = new Job + void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + try { - Id = job.Id - }; + var oldJob = job; + job = new Job { Id = oldJob.Id }; - databaseContext.Jobs.Attach(attachedJob); - attachedJob.StoppedAt = DateTimeOffset.Now; - attachedJob.ExceptionDetails = job.ExceptionDetails; - attachedJob.ErrorCode = job.ErrorCode; - attachedJob.Cancelled = job.Cancelled; + await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false); - await databaseContext.Save(default).ConfigureAwait(false); - }).ConfigureAwait(false); - } - finally - { - lock (synchronizationLock) - { - var handler = jobs[job.Id]; - jobs.Remove(job.Id); - handler.Dispose(); + logger.LogDebug("Job {0} completed!", job.Id); + } + catch (OperationCanceledException) + { + logger.LogDebug("Job {0} cancelled!", job.Id); + job.Cancelled = true; + } + catch (JobException e) + { + job.ErrorCode = e.ErrorCode; + job.ExceptionDetails = e.Message; + LogRegularException(); + if (e.InnerException != null) + logger.LogDebug( + "Inner exception for job {0}: {1}", + job.Id, + e.InnerException is JobException + ? e.InnerException.Message + : e.InnerException.ToString()); + } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + LogRegularException(); + } + + await databaseContextFactory.UseContext(async databaseContext => + { + var attachedJob = new Job + { + Id = job.Id + }; + + databaseContext.Jobs.Attach(attachedJob); + attachedJob.StoppedAt = DateTimeOffset.Now; + attachedJob.ExceptionDetails = job.ExceptionDetails; + attachedJob.ErrorCode = job.ErrorCode; + attachedJob.Cancelled = job.Cancelled; + + await databaseContext.Save(default).ConfigureAwait(false); + }).ConfigureAwait(false); + } + finally + { + lock (synchronizationLock) + { + var handler = jobs[job.Id]; + jobs.Remove(job.Id); + handler.Dispose(); + } } - } } /// diff --git a/src/Tgstation.Server.Host/Models/ChatBot.cs b/src/Tgstation.Server.Host/Models/ChatBot.cs index 27cdcfaa89..b34876bb40 100644 --- a/src/Tgstation.Server.Host/Models/ChatBot.cs +++ b/src/Tgstation.Server.Host/Models/ChatBot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models public const ushort DefaultChannelLimit = 100; /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 1285c4c49e..d7b49c1c76 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 0137be1678..2d92d57fa1 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/DualReattachInformation.cs b/src/Tgstation.Server.Host/Models/DualReattachInformation.cs index 763e5d10a3..0669e4f88c 100644 --- a/src/Tgstation.Server.Host/Models/DualReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/DualReattachInformation.cs @@ -11,7 +11,7 @@ public long Id { get; set; } /// - /// The of the the belongs to + /// The of the the belongs to /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 7b9b8491d1..2aa74e5042 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The of + /// The of /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 5493ae0fbe..df1b51305e 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 02dd5edef3..f1d3a2c970 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models public long Id { get; set; } /// - /// The + /// The instance /// public long InstanceId { get; set; } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index 39afcb925c..da9ea886de 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Security /// Create an to populate /// /// The of the - /// The of the operation + /// The of the operation /// The the resulting 's password must be valid after /// The for the operation /// A representing the running operation diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 4382558c26..af803d5cca 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -68,7 +68,7 @@ - +