From 4d5880a3b8026f023ba32e44715f0a54afe3eb2c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:33:06 -0400 Subject: [PATCH 1/9] Add a note to the readme about discord bot perms --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd2898ed40..636df3d49c 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ DreamDaemon can be finicky and will crash with several high load games or bad DM TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported - Internet Relay Chat (IRC) -- Discord +- Discord (Bot requires perms to chat and edit own messages) More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface From 13b71970a8c4369d3aa3f99cd5c8cc509fe2d56b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:47:20 -0400 Subject: [PATCH 2/9] Fix auto update change logic --- src/Tgstation.Server.Host/Components/Instance.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 76d67ce595..4ca3203aa6 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -438,8 +438,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 +451,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); } From bc1ced946871a67a85672e1d3cf7b8d3ac0e5e82 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:48:39 -0400 Subject: [PATCH 3/9] This is dumb --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 636df3d49c..cd2898ed40 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ DreamDaemon can be finicky and will crash with several high load games or bad DM TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported - Internet Relay Chat (IRC) -- Discord (Bot requires perms to chat and edit own messages) +- Discord More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface From fce15e99b711bee7dcc09ce3f371a9852e3149bf Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:50:49 -0400 Subject: [PATCH 4/9] Improve log category --- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 4ca3203aa6..3a0ba7eb61 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -151,7 +151,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 { From 4e90c937c207a5c87126b99de0b7497be33419ff Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:50:58 -0400 Subject: [PATCH 5/9] Reword heartbeat failure messages --- .../Components/Watchdog/WatchdogBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d2400a8170..c603cde0d6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -289,10 +289,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 +300,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; From 635be097b4b1de68a964125c1197ca1dcbb06e75 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:55:33 -0400 Subject: [PATCH 6/9] Fix log file names --- src/Tgstation.Server.Host/Core/Application.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7f486eeb43..8632cf6209 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -136,7 +136,7 @@ namespace Tgstation.Server.Host.Core "{Timestamp:o} {RequestId,13} [{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, From 716ecc8dbe8d3f036f45e1af12d09a3b6bab9896 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 10:58:15 -0400 Subject: [PATCH 7/9] Minor comment --- src/Tgstation.Server.Host/Core/Application.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 8632cf6209..74a1e178f1 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -167,6 +167,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(); From b2f10d8721fdc6cacdcf2be2ecda87efd92d89ed Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 13:39:25 -0400 Subject: [PATCH 8/9] Chungus - Made API Instance inherit EntityId - Added Serilog context logging - Updated Octokit - Fixed instance renaming not sending DMAPI notifications - Fixed socket bind test --- src/Tgstation.Server.Api/ApiHeaders.cs | 4 +- src/Tgstation.Server.Api/Models/EntityId.cs | 2 +- src/Tgstation.Server.Api/Models/Instance.cs | 7 +- .../Components/Chat/ChatManager.cs | 10 +- .../Components/Chat/Message.cs | 2 +- .../Components/IInstance.cs | 8 +- .../Components/IRenameNotifyee.cs | 19 ++ .../Components/Instance.cs | 49 ++-- .../Components/InstanceManager.cs | 3 +- .../Interop/Bridge/IBridgeHandler.cs | 13 +- .../Session/DeadSessionController.cs | 3 + .../Components/Session/ISessionController.cs | 2 +- .../Components/Session/SessionController.cs | 255 +++++++++--------- .../Session/SessionControllerFactory.cs | 4 +- .../Components/Watchdog/BasicWatchdog.cs | 4 + .../Watchdog/ExperimentalWatchdog.cs | 6 + .../Components/Watchdog/IWatchdog.cs | 2 +- .../Components/Watchdog/WatchdogBase.cs | 239 ++++++++-------- .../Controllers/ApiController.cs | 35 +-- .../Controllers/BridgeController.cs | 54 ++-- .../Controllers/InstanceController.cs | 6 +- src/Tgstation.Server.Host/Core/Application.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 10 +- src/Tgstation.Server.Host/Jobs/JobManager.cs | 112 ++++---- src/Tgstation.Server.Host/Models/ChatBot.cs | 2 +- .../Models/DreamDaemonSettings.cs | 2 +- .../Models/DreamMakerSettings.cs | 2 +- .../Models/DualReattachInformation.cs | 2 +- .../Models/InstanceUser.cs | 2 +- .../Models/RepositorySettings.cs | 2 +- .../Models/RevisionInformation.cs | 2 +- .../Security/IAuthenticationContextFactory.cs | 2 +- .../Tgstation.Server.Host.csproj | 2 +- 33 files changed, 475 insertions(+), 396 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IRenameNotifyee.cs 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.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 3a0ba7eb61..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(); + } } /// @@ -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); + } } /// 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 c603cde0d6..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; @@ -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 74a1e178f1..edfddae43f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -133,7 +133,9 @@ 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-.log"); 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 @@ - + From 0ddd47ce711e467fa7fc08edc231ac6c40aa107d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 8 Jun 2020 14:02:22 -0400 Subject: [PATCH 9/9] Fix build errors --- src/Tgstation.Server.Client/ApiClient.cs | 2 +- src/Tgstation.Server.Client/IApiClient.cs | 16 ++++++++-------- .../IInstanceManagerClient.cs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) 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);