From 907b3e42c49d154ecbe874dd769b953100c759e8 Mon Sep 17 00:00:00 2001 From: Michael Green <84688932+michael-j-green@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:18:21 +1100 Subject: [PATCH] Added log correlation and revised background tasks layout (#230) --- gaseous-server/Classes/Common.cs | 26 ++ gaseous-server/Classes/Logging.cs | 50 ++- .../Classes/SignatureIngestors/XML.cs | 367 +++++++++--------- gaseous-server/ProcessQueue.cs | 73 +++- .../Support/Database/MySQL/gaseous-1008.sql | 8 +- gaseous-server/wwwroot/index.html | 2 +- gaseous-server/wwwroot/pages/settings.html | 4 + .../wwwroot/pages/settings/logs.html | 34 +- .../wwwroot/pages/settings/system.html | 122 +++--- gaseous-server/wwwroot/styles/style.css | 24 +- 10 files changed, 460 insertions(+), 250 deletions(-) diff --git a/gaseous-server/Classes/Common.cs b/gaseous-server/Classes/Common.cs index 5a2e893..81d5dea 100644 --- a/gaseous-server/Classes/Common.cs +++ b/gaseous-server/Classes/Common.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Security.Cryptography; namespace gaseous_server.Classes @@ -111,5 +112,30 @@ namespace gaseous_server.Classes .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); } } + + /// + /// Provides a way to set contextual data that flows with the call and + /// async context of a test or invocation. + /// + public static class CallContext + { + static ConcurrentDictionary> state = new ConcurrentDictionary>(); + + /// + /// Stores a given object and associates it with the specified name. + /// + /// The name with which to associate the new item in the call context. + /// The object to store in the call context. + public static void SetData(string name, object data) => + state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; + + /// + /// Retrieves an object with the specified name from the . + /// + /// The name of the item in the call context. + /// The object in the call context associated with the specified name, or if not found. + public static object GetData(string name) => + state.TryGetValue(name, out AsyncLocal data) ? data.Value : null; + } } diff --git a/gaseous-server/Classes/Logging.cs b/gaseous-server/Classes/Logging.cs index 65218ab..dfa9447 100644 --- a/gaseous-server/Classes/Logging.cs +++ b/gaseous-server/Classes/Logging.cs @@ -75,8 +75,28 @@ namespace gaseous_server.Classes LogToDisk(logItem, TraceOutput, null); } + string correlationId; + if (CallContext.GetData("CorrelationId").ToString() == null) + { + correlationId = ""; + } + else + { + correlationId = CallContext.GetData("CorrelationId").ToString(); + } + + string callingProcess; + if (CallContext.GetData("CallingProcess").ToString() == null) + { + callingProcess = ""; + } + else + { + callingProcess = CallContext.GetData("CallingProcess").ToString(); + } + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate; INSERT INTO ServerLogs (EventTime, EventType, Process, Message, Exception) VALUES (@EventTime, @EventType, @Process, @Message, @Exception);"; + string sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate; INSERT INTO ServerLogs (EventTime, EventType, Process, Message, Exception, CorrelationId, CallingProcess) VALUES (@EventTime, @EventType, @Process, @Message, @Exception, @correlationid, @callingprocess);"; Dictionary dbDict = new Dictionary(); dbDict.Add("EventRententionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); dbDict.Add("EventTime", logItem.EventTime); @@ -84,6 +104,8 @@ namespace gaseous_server.Classes dbDict.Add("Process", logItem.Process); dbDict.Add("Message", logItem.Message); dbDict.Add("Exception", Common.ReturnValueIfNull(logItem.ExceptionValue, "").ToString()); + dbDict.Add("correlationid", correlationId); + dbDict.Add("callingprocess", callingProcess); try { @@ -184,6 +206,24 @@ namespace gaseous_server.Classes } } + if (model.CorrelationId != null) + { + if (model.CorrelationId.Length > 0) + { + dbDict.Add("correlationId", model.CorrelationId); + whereClauses.Add("CorrelationId = @correlationId"); + } + } + + if (model.CallingProcess != null) + { + if (model.CallingProcess.Length > 0) + { + dbDict.Add("callingProcess", model.CallingProcess); + whereClauses.Add("CallingProcess = @callingProcess"); + } + } + // compile WHERE clause string whereClause = ""; if (whereClauses.Count > 0) @@ -220,7 +260,9 @@ namespace gaseous_server.Classes EventType = (LogType)row["EventType"], Process = (string)row["Process"], Message = (string)row["Message"], - ExceptionValue = (string)row["Exception"] + ExceptionValue = (string)row["Exception"], + CorrelationId = (string)row["CorrelationId"], + CallingProcess = (string)row["CallingProcess"] }; logs.Add(log); @@ -243,6 +285,8 @@ namespace gaseous_server.Classes public DateTime EventTime { get; set; } public LogType? EventType { get; set; } public string Process { get; set; } = ""; + public string CorrelationId { get; set; } = ""; + public string? CallingProcess { get; set; } = ""; private string _Message = ""; public string Message { @@ -267,6 +311,8 @@ namespace gaseous_server.Classes public DateTime? StartDateTime { get; set; } public DateTime? EndDateTime { get; set; } public string? SearchText { get; set; } + public string? CorrelationId { get; set; } + public string? CallingProcess { get; set; } } } } diff --git a/gaseous-server/Classes/SignatureIngestors/XML.cs b/gaseous-server/Classes/SignatureIngestors/XML.cs index 8378df0..eda4fdc 100644 --- a/gaseous-server/Classes/SignatureIngestors/XML.cs +++ b/gaseous-server/Classes/SignatureIngestors/XML.cs @@ -33,221 +33,228 @@ namespace gaseous_server.SignatureIngestors.XML SetStatus(i + 1, PathContents.Length, "Processing signature file: " + XMLFile); - // check xml file md5 - Common.hashObject hashObject = new Common.hashObject(XMLFile); - sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; - dbDict = new Dictionary(); - dbDict.Add("sourcemd5", hashObject.md5hash); - sigDB = db.ExecuteCMD(sql, dbDict); - - if (sigDB.Rows.Count == 0) + if (Common.SkippableFiles.Contains(Path.GetFileName(XMLFile), StringComparer.OrdinalIgnoreCase)) { - try + Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Skipping file: " + XMLFile); + } + else + { + // check xml file md5 + Common.hashObject hashObject = new Common.hashObject(XMLFile); + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; + dbDict = new Dictionary(); + dbDict.Add("sourcemd5", hashObject.md5hash); + sigDB = db.ExecuteCMD(sql, dbDict); + + if (sigDB.Rows.Count == 0) { - Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing file: " + XMLFile); - - // start parsing file - gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser(); - RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, XMLType); - - // store in database - string[] flipNameAndDescription = { - "MAMEArcade", - "MAMEMess" - }; - - // store source object - bool processGames = false; - if (Object.SourceMd5 != null) + try { - sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; - dbDict = new Dictionary(); - string sourceUriStr = ""; - if (Object.Url != null) + Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing file: " + XMLFile); + + // start parsing file + gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser(); + RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, XMLType); + + // store in database + string[] flipNameAndDescription = { + "MAMEArcade", + "MAMEMess" + }; + + // store source object + bool processGames = false; + if (Object.SourceMd5 != null) { - sourceUriStr = Object.Url.ToString(); - } - dbDict.Add("name", Common.ReturnValueIfNull(Object.Name, "")); - dbDict.Add("description", Common.ReturnValueIfNull(Object.Description, "")); - dbDict.Add("category", Common.ReturnValueIfNull(Object.Category, "")); - dbDict.Add("version", Common.ReturnValueIfNull(Object.Version, "")); - dbDict.Add("author", Common.ReturnValueIfNull(Object.Author, "")); - dbDict.Add("email", Common.ReturnValueIfNull(Object.Email, "")); - dbDict.Add("homepage", Common.ReturnValueIfNull(Object.Homepage, "")); - dbDict.Add("uri", sourceUriStr); - dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, "")); - dbDict.Add("sourcemd5", Object.SourceMd5); - dbDict.Add("sourcesha1", Object.SourceSHA1); - - sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) - { - // entry not present, insert it - sql = "INSERT INTO Signatures_Sources (Name, Description, Category, Version, Author, Email, Homepage, Url, SourceType, SourceMD5, SourceSHA1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; - - db.ExecuteCMD(sql, dbDict); - - processGames = true; - } - - if (processGames == true) - { - for (int x = 0; x < Object.Games.Count; ++x) + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; + dbDict = new Dictionary(); + string sourceUriStr = ""; + if (Object.Url != null) { - RomSignatureObject.Game gameObject = Object.Games[x]; + sourceUriStr = Object.Url.ToString(); + } + dbDict.Add("name", Common.ReturnValueIfNull(Object.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(Object.Description, "")); + dbDict.Add("category", Common.ReturnValueIfNull(Object.Category, "")); + dbDict.Add("version", Common.ReturnValueIfNull(Object.Version, "")); + dbDict.Add("author", Common.ReturnValueIfNull(Object.Author, "")); + dbDict.Add("email", Common.ReturnValueIfNull(Object.Email, "")); + dbDict.Add("homepage", Common.ReturnValueIfNull(Object.Homepage, "")); + dbDict.Add("uri", sourceUriStr); + dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, "")); + dbDict.Add("sourcemd5", Object.SourceMd5); + dbDict.Add("sourcesha1", Object.SourceSHA1); - // set up game dictionary - dbDict = new Dictionary(); - if (flipNameAndDescription.Contains(Object.SourceType)) - { - dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, "")); - dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, "")); - } - else - { - dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); - dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); - } - dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); - dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); - dbDict.Add("demo", (int)gameObject.Demo); - dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); - dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); - dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); - dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); - dbDict.Add("country", Common.ReturnValueIfNull(gameObject.Country, "")); - dbDict.Add("language", Common.ReturnValueIfNull(gameObject.Language, "")); - dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, "")); + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Sources (Name, Description, Category, Version, Author, Email, Homepage, Url, SourceType, SourceMD5, SourceSHA1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; - // store platform - int gameSystem = 0; - if (gameObject.System != null) - { - sql = "SELECT Id FROM Signatures_Platforms WHERE Platform=@platform"; + db.ExecuteCMD(sql, dbDict); - sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) + processGames = true; + } + + if (processGames == true) + { + for (int x = 0; x < Object.Games.Count; ++x) + { + RomSignatureObject.Game gameObject = Object.Games[x]; + + // set up game dictionary + dbDict = new Dictionary(); + if (flipNameAndDescription.Contains(Object.SourceType)) { - // entry not present, insert it - sql = "INSERT INTO Signatures_Platforms (Platform) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; - sigDB = db.ExecuteCMD(sql, dbDict); - - gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, "")); } else { - gameSystem = (int)sigDB.Rows[0][0]; + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); } - } - dbDict.Add("systemid", gameSystem); + dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); + dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); + dbDict.Add("demo", (int)gameObject.Demo); + dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); + dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); + dbDict.Add("country", Common.ReturnValueIfNull(gameObject.Country, "")); + dbDict.Add("language", Common.ReturnValueIfNull(gameObject.Language, "")); + dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, "")); - // store publisher - int gamePublisher = 0; - if (gameObject.Publisher != null) - { - sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; - - sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) + // store platform + int gameSystem = 0; + if (gameObject.System != null) { - // entry not present, insert it - sql = "INSERT INTO Signatures_Publishers (Publisher) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; - sigDB = db.ExecuteCMD(sql, dbDict); - gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); - } - else - { - gamePublisher = (int)sigDB.Rows[0][0]; - } - } - dbDict.Add("publisherid", gamePublisher); - - // store game - int gameId = 0; - sql = "SELECT * FROM Signatures_Games WHERE Name=@name AND Year=@year AND Publisherid=@publisher AND Systemid=@systemid AND Country=@country AND Language=@language"; - - sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) - { - // entry not present, insert it - sql = "INSERT INTO Signatures_Games " + - "(Name, Description, Year, PublisherId, Demo, SystemId, SystemVariant, Video, Country, Language, Copyright) VALUES " + - "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; - sigDB = db.ExecuteCMD(sql, dbDict); - - gameId = Convert.ToInt32(sigDB.Rows[0][0]); - } - else - { - gameId = (int)sigDB.Rows[0][0]; - } - - // store rom - foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) - { - if (romObject.Md5 != null || romObject.Sha1 != null) - { - int romId = 0; - sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5"; - dbDict = new Dictionary(); - dbDict.Add("gameid", gameId); - dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); - dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); - dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower()); - dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower()); - dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower()); - dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); - - if (romObject.Attributes != null) - { - if (romObject.Attributes.Count > 0) - { - dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes)); - } - else - { - dbDict.Add("attributes", "[ ]"); - } - } - else - { - dbDict.Add("attributes", "[ ]"); - } - dbDict.Add("romtype", (int)romObject.RomType); - dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); - dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); - dbDict.Add("metadatasource", romObject.SignatureSource); - dbDict.Add("ingestorversion", 2); + sql = "SELECT Id FROM Signatures_Platforms WHERE Platform=@platform"; sigDB = db.ExecuteCMD(sql, dbDict); if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, MetadataSource, IngestorVersion) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sql = "INSERT INTO Signatures_Platforms (Platform) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - - romId = Convert.ToInt32(sigDB.Rows[0][0]); + gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); } else { - romId = (int)sigDB.Rows[0][0]; + gameSystem = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("systemid", gameSystem); + + // store publisher + int gamePublisher = 0; + if (gameObject.Publisher != null) + { + sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Publishers (Publisher) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gamePublisher = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("publisherid", gamePublisher); + + // store game + int gameId = 0; + sql = "SELECT * FROM Signatures_Games WHERE Name=@name AND Year=@year AND Publisherid=@publisher AND Systemid=@systemid AND Country=@country AND Language=@language"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Games " + + "(Name, Description, Year, PublisherId, Demo, SystemId, SystemVariant, Video, Country, Language, Copyright) VALUES " + + "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gameId = (int)sigDB.Rows[0][0]; + } + + // store rom + foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) + { + if (romObject.Md5 != null || romObject.Sha1 != null) + { + int romId = 0; + sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5"; + dbDict = new Dictionary(); + dbDict.Add("gameid", gameId); + dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); + dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); + dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower()); + dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower()); + dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower()); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); + + if (romObject.Attributes != null) + { + if (romObject.Attributes.Count > 0) + { + dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes)); + } + else + { + dbDict.Add("attributes", "[ ]"); + } + } + else + { + dbDict.Add("attributes", "[ ]"); + } + dbDict.Add("romtype", (int)romObject.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); + dbDict.Add("metadatasource", romObject.SignatureSource); + dbDict.Add("ingestorversion", 2); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, MetadataSource, IngestorVersion) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + + romId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + romId = (int)sigDB.Rows[0][0]; + } } } } } } } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Warning, "Signature Ingestor - XML", "Invalid import file: " + XMLFile, ex); + } } - catch (Exception ex) + else { - Logging.Log(Logging.LogType.Warning, "Signature Ingestor - XML", "Invalid import file: " + XMLFile, ex); + Logging.Log(Logging.LogType.Debug, "Signature Ingestor - XML", "Rejecting already imported file: " + XMLFile); } } - else - { - Logging.Log(Logging.LogType.Debug, "Signature Ingestor - XML", "Rejecting already imported file: " + XMLFile); - } } ClearStatus(); } diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index b2f99fb..de4d121 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -1,4 +1,6 @@ using System; +using System.ComponentModel.Design.Serialization; +using System.Data; using gaseous_server.Classes; namespace gaseous_server @@ -56,6 +58,7 @@ namespace gaseous_server private bool _AllowManualStart = true; private bool _RemoveWhenStopped = false; private bool _IsBlocked = false; + private string _CorrelationId = ""; private List _Blocks = new List(); public QueueItemType ItemType => _ItemType; @@ -90,6 +93,7 @@ namespace gaseous_server public object? Options { get; set; } = null; public string CurrentState { get; set; } = ""; public string CurrentStateProgress { get; set; } = ""; + public string CorrelationId => _CorrelationId; public List Blocks => _Blocks; public void Execute() @@ -104,7 +108,14 @@ namespace gaseous_server _LastResult = ""; _LastError = null; - Logging.Log(Logging.LogType.Debug, "Timered Event", "Executing " + _ItemType); + // set the correlation id + Guid correlationId = Guid.NewGuid(); + _CorrelationId = correlationId.ToString(); + CallContext.SetData("CorrelationId", correlationId); + CallContext.SetData("CallingProcess", _ItemType.ToString()); + + // log the start + Logging.Log(Logging.LogType.Debug, "Timered Event", "Executing " + _ItemType + " with correlation id " + _CorrelationId); try { @@ -238,6 +249,66 @@ namespace gaseous_server { _IsBlocked = BlockState; } + + public HasErrorsItem HasErrors + { + get + { + return new HasErrorsItem(_CorrelationId); + } + } + + public class HasErrorsItem + { + public HasErrorsItem(string? CorrelationId) + { + if (CorrelationId != null) + { + if (CorrelationId.Length > 0) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT EventType, COUNT(EventType) AS EventTypes FROM gaseous.ServerLogs WHERE CorrelationId = @correlationid GROUP BY EventType ORDER BY EventType DESC LIMIT 1;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("correlationid", CorrelationId); + + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count == 0) + { + ErrorType = null; + ErrorCount = 0; + } + else + { + Logging.LogType errorType = (Logging.LogType)data.Rows[0]["EventType"]; + if (errorType != Logging.LogType.Information) + { + ErrorType = errorType; + ErrorCount = (int)(long)data.Rows[0]["EventTypes"]; + } + else + { + ErrorType = null; + ErrorCount = 0; + } + } + } + else + { + ErrorType = null; + ErrorCount = 0; + } + } + else + { + ErrorType = null; + ErrorCount = 0; + } + } + + public Logging.LogType? ErrorType { get; set; } + public int ErrorCount { get; set; } + } } public enum QueueItemType diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1008.sql b/gaseous-server/Support/Database/MySQL/gaseous-1008.sql index a0fb9a7..ed3c3fe 100644 --- a/gaseous-server/Support/Database/MySQL/gaseous-1008.sql +++ b/gaseous-server/Support/Database/MySQL/gaseous-1008.sql @@ -17,4 +17,10 @@ ALTER TABLE `Relation_Game_PlayerPerspectives` ADD INDEX `idx_SecondaryColumn` (`PlayerPerspectivesId` ASC) VISIBLE; ALTER TABLE `Relation_Game_Themes` -ADD INDEX `idx_SecondaryColumn` (`ThemesId` ASC) VISIBLE; \ No newline at end of file +ADD INDEX `idx_SecondaryColumn` (`ThemesId` ASC) VISIBLE; + +ALTER TABLE `ServerLogs` +ADD COLUMN `CorrelationId` VARCHAR(45) NULL AFTER `Exception`, +ADD COLUMN `CallingProcess` VARCHAR(45) NULL AFTER `CorrelationId`, +ADD INDEX `idx_CorrelationId` (`CorrelationId` ASC) VISIBLE, +ADD INDEX `idx_CallingProcess` (`CallingProcess` ASC) VISIBLE; \ No newline at end of file diff --git a/gaseous-server/wwwroot/index.html b/gaseous-server/wwwroot/index.html index b1764f6..848c5de 100644 --- a/gaseous-server/wwwroot/index.html +++ b/gaseous-server/wwwroot/index.html @@ -3,7 +3,7 @@ - + diff --git a/gaseous-server/wwwroot/pages/settings.html b/gaseous-server/wwwroot/pages/settings.html index 0eb93a5..74cd83e 100644 --- a/gaseous-server/wwwroot/pages/settings.html +++ b/gaseous-server/wwwroot/pages/settings.html @@ -47,6 +47,10 @@ SelectTab(selectedTab); function SelectTab(TabName) { + if (selectedTab != TabName) { + window.location.href = '/index.html?page=settings&sub=' + TabName; + } + var tocs = document.getElementsByName('properties_toc_item'); for (var i = 0; i < tocs.length; i++) { if ((tocs[i].id) == ("properties_toc_" + TabName)) { diff --git a/gaseous-server/wwwroot/pages/settings/logs.html b/gaseous-server/wwwroot/pages/settings/logs.html index 051cb43..d1a4fcc 100644 --- a/gaseous-server/wwwroot/pages/settings/logs.html +++ b/gaseous-server/wwwroot/pages/settings/logs.html @@ -5,10 +5,7 @@ - - + + - + + + @@ -43,6 +47,13 @@ var currentPage = 1; var searchModel = {}; + var correlationIdParam = getQueryString('correlationid', 'string'); + if (correlationIdParam) { + if (correlationIdParam.length > 0) { + document.getElementById('logs_correlationid').value = correlationIdParam; + } + } + function resetFilters() { document.getElementById('logs_startdate').value = ''; document.getElementById('logs_enddate').value = ''; @@ -50,6 +61,7 @@ document.getElementById('logs_type_warning').checked = false; document.getElementById('logs_type_critical').checked = false; document.getElementById('logs_textsearch').value = ''; + document.getElementById('logs_correlationid').value = ''; loadLogs(); } @@ -81,6 +93,9 @@ var searchText = null; var searchTextObj = document.getElementById('logs_textsearch'); if (searchTextObj.value != null) { searchText = searchTextObj.value; } + var correlationId = null; + var correlationIdTextObj = document.getElementById('logs_correlationid'); + if (correlationIdTextObj.value != null) { correlationId = correlationIdTextObj.value; } model = { "StartIndex": StartIndex, @@ -89,7 +104,8 @@ "Status": statusList, "StartDateTime": startDate, "EndDateTime": endDate, - "SearchText": searchText + "SearchText": searchText, + "CorrelationId": correlationId } searchModel = model; } @@ -131,7 +147,7 @@ result[i].message ]; - newTable.appendChild(createTableRow(false, newRow, 'romrow logs_table_row_' + result[i].eventType, 'romcell logs_table_cell')); + newTable.appendChild(createTableRow(false, newRow, 'logs_table_row_' + result[i].eventType, 'romcell logs_table_cell')); if (result[i].exceptionValue) { var exceptionString = "

Exception

" + syntaxHighlight(JSON.stringify(result[i].exceptionValue, null, 2)).replace(/\\n/g, "
") + "
"; diff --git a/gaseous-server/wwwroot/pages/settings/system.html b/gaseous-server/wwwroot/pages/settings/system.html index d653d85..01c859f 100644 --- a/gaseous-server/wwwroot/pages/settings/system.html +++ b/gaseous-server/wwwroot/pages/settings/system.html @@ -31,66 +31,84 @@ var newTable = document.createElement('table'); newTable.className = 'romtable'; newTable.setAttribute('cellspacing', 0); - newTable.appendChild(createTableRow(true, ['Task', 'Status', 'Interval', 'Last Run Start', 'Last Run Duration (seconds)', 'Next Run Start', ''])); + newTable.appendChild(createTableRow(true, ['Task', 'Status', 'Interval
(minutes)', 'Last Run Duration
(hh:mm:ss)', '', 'Last Run Start', 'Next Run Start', ''])); if (result) { for (var i = 0; i < result.length; i++) { - var itemTypeName = GetTaskFriendlyName(result[i].itemType, result[i].options); - - var itemStateName; - var itemLastStart; - if (result[i].isBlocked == false) { - switch (result[i].itemState) { - case 'NeverStarted': - itemStateName = "Never started"; - itemLastStart = '-'; - break; - case 'Stopped': - itemStateName = "Stopped"; - itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); - break; - case 'Running': - var progressPercent = ""; - if (result[i].currentStateProgress) { - progressPercent = " (" + result[i].currentStateProgress + ")"; - } - itemStateName = "Running" + progressPercent; - itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); - break; - default: - itemStateName = "Unknown status"; - itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); - break; + if (result[i].itemState != "Disabled") { + var itemTypeName = GetTaskFriendlyName(result[i].itemType, result[i].options); + + var itemStateName; + var itemLastStart; + + var hasError = ""; + if (result[i].hasErrors) { + if (result[i].hasErrors.errorType != null) { + hasError = " (" + result[i].hasErrors.errorType + ")"; + } } - } else { - itemStateName = "Blocked"; - itemLastStart = moment(result[i].lastRunTime).fromNow(); - } - var itemInterval = result[i].interval; - var nextRunTime = moment(result[i].nextRunTime).format("YYYY-MM-DD h:mm:ss a"); - var startButton = ''; - if (userProfile.roles.includes("Admin")) { - if (result[i].allowManualStart == true && result[i].itemState != "Running") { - startButton = "Start"; + if (result[i].isBlocked == false) { + switch (result[i].itemState) { + case 'NeverStarted': + itemStateName = "Never started"; + itemLastStart = '-'; + break; + case 'Stopped': + itemStateName = "Stopped"; + itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); + break; + case 'Running': + var progressPercent = ""; + if (result[i].currentStateProgress) { + progressPercent = " (" + result[i].currentStateProgress + ")"; + } + itemStateName = "Running" + progressPercent; + itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); + break; + default: + itemStateName = "Unknown status"; + itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); + break; + } + } else { + itemStateName = "Blocked"; + itemLastStart = moment(result[i].lastRunTime).format("YYYY-MM-DD h:mm:ss a"); } - } - if (result[i].allowManualStart == false && result[i].removeWhenStopped == true) { - itemInterval = ''; - nextRunTime = ''; - } + itemStateName += hasError; - var newRow = [ - itemTypeName, - itemStateName, - itemInterval, - itemLastStart, - result[i].lastRunDuration, - nextRunTime, - startButton - ]; - newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell')); + var itemInterval = result[i].interval; + var nextRunTime = moment(result[i].nextRunTime).format("YYYY-MM-DD h:mm:ss a"); + var startButton = ''; + if (userProfile.roles.includes("Admin")) { + if (result[i].allowManualStart == true && result[i].itemState != "Running") { + startButton = "Start"; + } + } + + if (result[i].allowManualStart == false && result[i].removeWhenStopped == true) { + itemInterval = ''; + nextRunTime = ''; + } + + var logLink = ''; + if (result[i].correlationId) { + logLink = 'View Log'; + } + + var newRow = [ + itemTypeName, + itemStateName, + itemInterval, + new Date(result[i].lastRunDuration * 1000).toISOString().slice(11, 19), + logLink, + itemLastStart, + nextRunTime, + startButton + ]; + newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell')); + } } } diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index 577a68c..3bfa03a 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -1060,22 +1060,38 @@ button:disabled { vertical-align: top; } -.logs_table_row_Information:hover { +.logs_table_row_Information:nth-child(even) { background: rgba(42, 41, 150, 0.3); } -.logs_table_row_Warning:hover { +.logs_table_row_Information:nth-child(odd) { + background: rgba(10, 9, 83, 0.3); +} + +.logs_table_row_Warning:nth-child(even) { background: rgba(139, 150, 41, 0.3); } -.logs_table_row_Critical:hover { +.logs_table_row_Warning:nth-child(odd) { + background: rgba(49, 53, 14, 0.3); +} + +.logs_table_row_Critical:nth-child(even) { background: rgba(150, 41, 41, 0.3); } -.logs_table_row_Debug:hover { +.logs_table_row_Critical:nth-child(odd) { + background: rgba(58, 16, 16, 0.3); +} + +.logs_table_row_Debug:nth-child(even) { background: rgba(150, 41, 135, 0.3); } +.logs_table_row_Debug:nth-child(odd) { + background: rgba(68, 18, 61, 0.3); +} + .logs_table_exception { margin-right: 10px; padding: 5px;
- - - + @@ -19,10 +16,17 @@ - +
+ + + +