From 6a49a7d672acc298e2cbe70e80b569476b6d4792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BA=D0=BF=D0=B0=D0=B5=D0=B2=20=D0=95=D0=B2=D0=B3?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Thu, 28 Jan 2021 00:44:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BD=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClickHouseStorage.cs | 68 +-- .../Properties/Resources.Designer.cs | 8 +- .../Properties/Resources.resx | 11 +- .../ITechLogStorage.cs | 5 +- .../TechLogAnalyzer.cs | 99 ----- .../TechLogExporter.cs | 183 ++++---- OneSTools.TechLog.Exporter/Program.cs | 5 - OneSTools.TechLog.Exporter/StorageType.cs | 6 +- .../TechLogExporterService.cs | 9 - .../appsettings.Development.json | 6 +- OneSTools.TechLog.Exporter/appsettings.json | 2 +- OneSTools.TechLog.sln | 2 +- OneSTools.TechLog/AdditionalProperty.cs | 16 - .../DeadlockConnectionIntersectionsInfo.cs | 26 ++ .../DeadlockConnectionIntersectionsPair.cs | 36 ++ OneSTools.TechLog/LocksInfo.cs | 26 ++ OneSTools.TechLog/LocksInfoRegion.cs | 34 ++ .../LogReaderTimeoutException.cs | 9 - OneSTools.TechLog/OneSTools.TechLog.csproj | 2 +- OneSTools.TechLog/StreamReaderExtensions.cs | 12 +- OneSTools.TechLog/TechLogFileReader.cs | 240 +++-------- OneSTools.TechLog/TechLogItem.cs | 392 +++++++++++------- 22 files changed, 580 insertions(+), 617 deletions(-) delete mode 100644 OneSTools.TechLog.Exporter.Core/TechLogAnalyzer.cs delete mode 100644 OneSTools.TechLog/AdditionalProperty.cs create mode 100644 OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs create mode 100644 OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs create mode 100644 OneSTools.TechLog/LocksInfo.cs create mode 100644 OneSTools.TechLog/LocksInfoRegion.cs delete mode 100644 OneSTools.TechLog/LogReaderTimeoutException.cs diff --git a/OneSTools.TechLog.Exporter.ClickHouse/ClickHouseStorage.cs b/OneSTools.TechLog.Exporter.ClickHouse/ClickHouseStorage.cs index 508c420..a16fbfa 100644 --- a/OneSTools.TechLog.Exporter.ClickHouse/ClickHouseStorage.cs +++ b/OneSTools.TechLog.Exporter.ClickHouse/ClickHouseStorage.cs @@ -1,6 +1,5 @@ using ClickHouse.Client.ADO; using ClickHouse.Client.Copy; -using ClickHouse.Client.Utility; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using OneSTools.TechLog.Exporter.ClickHouse.Properties; @@ -8,70 +7,70 @@ using OneSTools.TechLog.Exporter.Core; using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Text.Json; namespace OneSTools.TechLog.Exporter.ClickHouse { public class ClickHouseStorage : ITechLogStorage { private readonly ILogger _logger; - private readonly string _connectionString; private readonly string _databaseName; private readonly ClickHouseConnection _connection; public ClickHouseStorage(ILogger logger, IConfiguration configuration) { _logger = logger; - _connectionString = configuration.GetValue("ClickHouse:ConnectionString", ""); + var connectionString = configuration.GetValue("ClickHouse:ConnectionString", ""); - if (_connectionString == string.Empty) + if (connectionString == string.Empty) throw new Exception("Connection string is not specified"); - _databaseName = Regex.Match(_connectionString, "(?<=Database=).*?(?=(;|$))", RegexOptions.IgnoreCase).Value; - _connectionString = Regex.Replace(_connectionString, "Database=.*?(;|$)", ""); + _databaseName = Regex.Match(connectionString, "(?<=Database=).*?(?=(;|$))", RegexOptions.IgnoreCase).Value; + connectionString = Regex.Replace(connectionString, "Database=.*?(;|$)", ""); if (string.IsNullOrWhiteSpace(_databaseName)) throw new Exception("Database name is not specified"); - _connection = new ClickHouseConnection(_connectionString); + _connection = new ClickHouseConnection(connectionString); EnsureCreated(); } public ClickHouseStorage(string connectionString, ILogger logger = null) { - _connectionString = connectionString; + var s = connectionString; _logger = logger; - if (_connectionString == string.Empty) + if (s == string.Empty) throw new Exception("Connection string is not specified"); - _databaseName = Regex.Match(_connectionString, "(?<=Database=).*?(?=(;|$))", RegexOptions.IgnoreCase).Value; - _connectionString = Regex.Replace(_connectionString, "Database=.*?(;|$)", ""); + _databaseName = Regex.Match(s, "(?<=Database=).*?(?=(;|$))", RegexOptions.IgnoreCase).Value; + s = Regex.Replace(s, "Database=.*?(;|$)", ""); if (string.IsNullOrWhiteSpace(_databaseName)) throw new Exception("Database name is not specified"); - _connection = new ClickHouseConnection(_connectionString); + _connection = new ClickHouseConnection(s); EnsureCreated(); } - public async Task WriteLastPositionAsync(string folder, string file, long position, CancellationToken cancellationToken = default) + public async Task WriteLastPositionAsync(string folder, string file, long position) { var cmd = _connection.CreateCommand(); var lastPosition = await GetLastPositionAsync(folder, file); + // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (lastPosition == 0) cmd.CommandText = $"INSERT INTO LastPositions (Folder, File, Position) VALUES ('{folder}', '{file}', {position})"; else cmd.CommandText = $"ALTER TABLE LastPositions UPDATE Position = {position} WHERE Folder = '{folder}' AND File = '{file}'"; - await cmd.ExecuteNonQueryAsync(cancellationToken); + await cmd.ExecuteNonQueryAsync(); } public async Task GetLastPositionAsync(string folder, string file, CancellationToken cancellationToken = default) @@ -79,7 +78,7 @@ namespace OneSTools.TechLog.Exporter.ClickHouse var cmd = _connection.CreateCommand(); cmd.CommandText = $"SELECT Position From LastPositions WHERE Folder='{folder}' AND File='{file}'"; - using var reader = await cmd.ExecuteReaderAsync(cancellationToken); + await using var reader = await cmd.ExecuteReaderAsync(cancellationToken); if (await reader.ReadAsync(cancellationToken)) return reader.GetInt64(0); @@ -87,29 +86,36 @@ namespace OneSTools.TechLog.Exporter.ClickHouse return 0; } - public async Task WriteItemsAsync(TechLogItem[] items, CancellationToken cancellationToken = default) + public async Task WriteItemsAsync(TechLogItem[] items) { var data = items.Select(item => new object[] { item.DateTime, + item.StartTicks, + item.EndTicks, item.EventName, item.Duration, - item.PProcessName, - item.TClientID, - item.TApplicationName, - item.TComputerName, - item.TConnectID, - item.Usr, - item.Sql.Replace('\'', '"'), - item.SqlHash, - item.Context.Replace('\'', '"'), - item.FirstContextLine.Replace('\'', '"'), - item.LastContextLine.Replace('\'', '"') + item.PProcessName ?? string.Empty, + item.SessionID ?? string.Empty, + item.TClientID ?? 0, + item.TApplicationName ?? string.Empty, + item.TComputerName ?? string.Empty, + item.TConnectID ?? 0, + item.Usr ?? string.Empty, + item.Sql?.Replace('\'', '"') ?? string.Empty, + item.SqlHash ?? string.Empty, + item.WaitConnections ?? string.Empty, + item.LocksInfo?.Regions.Select(c => c.Hash).ToArray() ?? new string[0], + item.DeadlockConnectionIntersectionsInfo is null ? string.Empty : JsonSerializer.Serialize(item.DeadlockConnectionIntersectionsInfo), + item.Context?.Replace('\'', '"') ?? string.Empty, + item.FirstContextLine?.Replace('\'', '"') ?? string.Empty, + item.LastContextLine?.Replace('\'', '"') ?? string.Empty, + item.Descr?.Replace('\'', '"') ?? string.Empty }).ToList(); - await WriteBulkAsync("Items", data, cancellationToken); + await WriteBulkAsync("Items", data); } - private async Task WriteBulkAsync(string tableName, List entities, CancellationToken cancellationToken = default) + private async Task WriteBulkAsync(string tableName, IReadOnlyCollection entities) { using var copy = new ClickHouseBulkCopy(_connection) { @@ -117,7 +123,7 @@ namespace OneSTools.TechLog.Exporter.ClickHouse BatchSize = entities.Count }; - await copy.WriteToServerAsync(entities, cancellationToken); + await copy.WriteToServerAsync(entities); } private void EnsureCreated() diff --git a/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.Designer.cs b/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.Designer.cs index 6d1a013..2a1955c 100644 --- a/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.Designer.cs +++ b/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.Designer.cs @@ -64,18 +64,18 @@ namespace OneSTools.TechLog.Exporter.ClickHouse.Properties { /// Ищет локализованную строку, похожую на CREATE TABLE IF NOT EXISTS Items ///( /// DateTime DateTime Codec(Delta, LZ4), + ///StartTicks Int64 Codec(DoubleDelta, LZ4), + ///EndTicks Int64 Codec(DoubleDelta, LZ4), /// EventName LowCardinality(String), /// Duration Int64 Codec(DoubleDelta, LZ4), /// PProcessName LowCardinality(String), + /// SessionID String Codec(ZSTD), /// TClientID Int32 Codec(DoubleDelta, LZ4), /// TApplicationName LowCardinality(String), /// TComputerName LowCardinality(String), /// TConnectID Int32 Codec(DoubleDelta, LZ4), /// Usr LowCardinality(String), - /// Sql String Codec(ZSTD), - /// SqlHash String Codec(ZSTD), - /// Context String Codec(ZSTD), - /// FirstContextLine String Codec(ZS [остаток строки не уместился]";. + /// Sq [остаток строки не уместился]";. /// internal static string CreateItemsTable { get { diff --git a/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.resx b/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.resx index c068abd..df740ab 100644 --- a/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.resx +++ b/OneSTools.TechLog.Exporter.ClickHouse/Properties/Resources.resx @@ -121,9 +121,12 @@ CREATE TABLE IF NOT EXISTS Items ( DateTime DateTime Codec(Delta, LZ4), +StartTicks Int64 Codec(DoubleDelta, LZ4), +EndTicks Int64 Codec(DoubleDelta, LZ4), EventName LowCardinality(String), Duration Int64 Codec(DoubleDelta, LZ4), PProcessName LowCardinality(String), + SessionID String Codec(ZSTD), TClientID Int32 Codec(DoubleDelta, LZ4), TApplicationName LowCardinality(String), TComputerName LowCardinality(String), @@ -131,12 +134,16 @@ Usr LowCardinality(String), Sql String Codec(ZSTD), SqlHash String Codec(ZSTD), + WaitConnections String Codec(ZSTD), + LocksHashes Array(String), +DeadlockInfo String Codec(ZSTD), Context String Codec(ZSTD), FirstContextLine String Codec(ZSTD), - LastContextLine String Codec(ZSTD) + LastContextLine String Codec(ZSTD), +Descr String Codec(ZSTD) ) engine = MergeTree() -ORDER BY (DateTime) +ORDER BY (DateTime, EndTicks, EventName) Создание таблицы для событий EXCP diff --git a/OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs b/OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs index 230a195..14e257e 100644 --- a/OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs +++ b/OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs @@ -1,14 +1,13 @@ using System; using System.Threading; -using System.Collections.Generic; using System.Threading.Tasks; namespace OneSTools.TechLog.Exporter.Core { public interface ITechLogStorage : IDisposable { - Task WriteLastPositionAsync(string folder, string file, long position, CancellationToken cancellationToken = default); + Task WriteLastPositionAsync(string folder, string file, long position); Task GetLastPositionAsync(string folder, string file, CancellationToken cancellationToken = default); - Task WriteItemsAsync(TechLogItem[] items, CancellationToken cancellationToken = default); + Task WriteItemsAsync(TechLogItem[] items); } } diff --git a/OneSTools.TechLog.Exporter.Core/TechLogAnalyzer.cs b/OneSTools.TechLog.Exporter.Core/TechLogAnalyzer.cs deleted file mode 100644 index 0f47d67..0000000 --- a/OneSTools.TechLog.Exporter.Core/TechLogAnalyzer.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Collections.ObjectModel; - -namespace OneSTools.TechLog.Exporter.Core -{ - public class DbmsDeadlockHandlerEventArgs - { - private readonly List _victims = new List(); - - public TechLogItem Source { get; private set; } - public ReadOnlyCollection Victims => _victims.AsReadOnly(); - - internal DbmsDeadlockHandlerEventArgs(TechLogItem source) - => Source = source; - - internal void AddVictim(TechLogItem victim) - => _victims.Add(victim); - } - - public class TDeadlockHandlerEventArgs - { - - } - - public class TechLogAnalyzer - { - private readonly Dictionary _deadlockSources = new Dictionary(); - private readonly Dictionary _unknownVictims = new Dictionary(); - - - public delegate void DbmsDeadlockHandler(object sender, DbmsDeadlockHandlerEventArgs a); - public event DbmsDeadlockHandler dbmsDeadlock; - - public delegate void TDeadlockHandler(object sender, DbmsDeadlockHandlerEventArgs a); - public event TDeadlockHandler Deadlock; - - public void HandleItem(TechLogItem item) - { - if (item.EventName == "DBMSSQL") - HandleDbms(item); - else if (item.EventName == "TDEADLOCK") - HandleTDeadlock(item); - } - - private void HandleDbms(TechLogItem item) - { - if (item.AllProperties.TryGetValue("lka", out var lka) && lka == "1") - { - // check this is a victim of the deadlock - if (item.AllProperties.TryGetValue("lkp", out var lkp) && lkp == "1") - { - // read the number of the query that blocked this query - var lkpid = item.AllProperties["lkpid"]; - //read the connection number that blocked this query - var lksrc = item.AllProperties["lksrc"]; - - // check there is a registered source of the deadblock - var deadlockSourceKey = $"{lksrc}|{lkpid}"; - - if (_deadlockSources.ContainsKey(deadlockSourceKey)) - { - // the source of the deadlock is registered - } - else - { - // the source of the deadblock is not registered, add this query to "Unknown victims" list - _unknownVictims.Add(deadlockSourceKey, item); - } - } - else - { - // get connection id of the query - var connectId = item.AllProperties["t:connectID"]; - - // it looks like this query is a real source of the deadblock, read victims' query numbers - var victimQueryNumbers = item.AllProperties["lkaid"]; - - var args = new DbmsDeadlockHandlerEventArgs(item); - - // try to get victims from "unknown victims" list - foreach (var victimNumber in victimQueryNumbers.Split(',')) - { - var key = $"{connectId}|{victimNumber}"; - - if (_unknownVictims.TryGetValue(key, out var victim)) - args.AddVictim(victim); - } - } - } - } - - private void HandleTDeadlock(TechLogItem item) - { - - } - } -} diff --git a/OneSTools.TechLog.Exporter.Core/TechLogExporter.cs b/OneSTools.TechLog.Exporter.Core/TechLogExporter.cs index 3ccff9c..ee851aa 100644 --- a/OneSTools.TechLog.Exporter.Core/TechLogExporter.cs +++ b/OneSTools.TechLog.Exporter.Core/TechLogExporter.cs @@ -10,55 +10,6 @@ using System.Threading.Tasks.Dataflow; namespace OneSTools.TechLog.Exporter.Core { - public class EventPropertiesMatrixCreator - { - public EventPropertiesMatrixCreator(string logFolder) - { - var data = new Dictionary>(); - - var files = Directory.GetFiles(logFolder, "*.log", SearchOption.AllDirectories); - - foreach (var file in files) - { - using var reader = new TechLogFileReader(file, AdditionalProperty.All); - - while (true) - { - var item = reader.ReadNextItem(); - - if (item is null) - break; - - var eventName = item.EventName; - - if (!data.TryGetValue(eventName, out var props)) - { - props = new List(); - data.Add(eventName, props); - } - - foreach (var property in item.AllProperties.Keys) - { - if (!props.Contains(property)) - props.Add(property); - } - } - } - - // find common properties - var common = new HashSet(); - - foreach (var props in data.Values) - foreach (var prop in props) - common.Add(prop); - - foreach (var props in data.Values) - common = common.Intersect(props).ToHashSet(); - - var strData = System.Text.Json.JsonSerializer.Serialize(data); - } - } - public class TechLogExporter : IDisposable { private readonly TechLogExporterSettings _settings; @@ -67,6 +18,7 @@ namespace OneSTools.TechLog.Exporter.Core private readonly HashSet _beingReadFiles = new HashSet(); private ActionBlock _writeBlock; + private TransformBlock _analyzerBlock; private ActionBlock _readBlock; private FileSystemWatcher _logFilesWatcher; private CancellationToken _cancellationToken; @@ -88,9 +40,7 @@ namespace OneSTools.TechLog.Exporter.Core _storage = storage; _logger = logger; - InitializeDataflow(); - - //var matrix = new EventPropertiesMatrixCreator(_settings.LogFolder); + InitializeDataFlow(); } public TechLogExporter(TechLogExporterSettings settings, ITechLogStorage storage, ILogger logger = null) @@ -99,7 +49,7 @@ namespace OneSTools.TechLog.Exporter.Core _storage = storage; _logger = logger; - InitializeDataflow(); + InitializeDataFlow(); } public async Task StartAsync(CancellationToken cancellationToken = default) @@ -124,7 +74,7 @@ namespace OneSTools.TechLog.Exporter.Core _logger?.LogInformation("Exporter is stopped"); } - private void InitializeDataflow(CancellationToken cancellationToken = default) + private void InitializeDataFlow(CancellationToken cancellationToken = default) { var writeBlockOptions = new ExecutionDataflowBlockOptions() { @@ -133,13 +83,21 @@ namespace OneSTools.TechLog.Exporter.Core }; _writeBlock = new ActionBlock(WriteItems, writeBlockOptions); + var analyzerBlockOptions = new ExecutionDataflowBlockOptions() + { + CancellationToken = cancellationToken, + BoundedCapacity = _settings.BatchFactor + }; + _analyzerBlock = new TransformBlock(AnalyzeItems, analyzerBlockOptions); + _analyzerBlock.LinkTo(_writeBlock, new DataflowLinkOptions { PropagateCompletion = true }); + var readBlockOptions = new ExecutionDataflowBlockOptions() { CancellationToken = cancellationToken, BoundedCapacity = DataflowBlockOptions.Unbounded }; _readBlock = new ActionBlock(ReadLogFileAsync, readBlockOptions); - _ = _readBlock.Completion.ContinueWith(c => _writeBlock.Complete()); + _ = _readBlock.Completion.ContinueWith(c => _analyzerBlock.Complete(), cancellationToken); } private async Task WriteItems(TechLogItem[] items) @@ -153,7 +111,7 @@ namespace OneSTools.TechLog.Exporter.Core await _storage.WriteLastPositionAsync(lastItem.FolderName, lastItem.FileName, lastItem.EndPosition); - _logger?.LogDebug($"{items.Length} events were being written"); + _logger?.LogDebug($"{DateTime.Now:HH:mm:ss.ffffff} {items.Length} events were being written"); } catch (TaskCanceledException) { } catch (Exception ex) @@ -162,23 +120,86 @@ namespace OneSTools.TechLog.Exporter.Core } } + private static TechLogItem[] AnalyzeItems(TechLogItem[] items) + { + return items; + + var setContext = false; + var context = ""; + int? tClientId = 0; + var firstEvent = false; + long startTicks = 0; + + for (var i = items.Length - 1; i >= 0; i--) + { + var item = items[i]; + + // If it reached Context event then it can set context in the chain of events + if (item.EventName == "Context") + { + setContext = true; + context = item.Context; + tClientId = item.TClientID; + firstEvent = true; + startTicks = 0; + + continue; + } + + if (setContext && item.TClientID == tClientId) + { + if (firstEvent) + { + if (item.EventName == "CALL") + { + firstEvent = false; + item.Context = context; + startTicks = item.StartTicks; + } + else + { + setContext = false; + context = ""; + tClientId = 0; + firstEvent = false; + startTicks = 0; + } + } + else + { + // Stop if it reached an earliest time than the CALL start time + if (item.DateTime.Ticks > startTicks) + item.Context = context; + else + { + setContext = false; + context = ""; + tClientId = 0; + startTicks = 0; + } + } + } + } + + return items; + } + private async Task ReadLogFileAsync(string filePath) { var items = new List(_settings.BatchSize); try { - using var reader = new TechLogFileReader(filePath, AdditionalProperty.All); + using var reader = new TechLogFileReader(filePath); var position = await _storage.GetLastPositionAsync(reader.FolderName, reader.FileName, _cancellationToken); reader.Position = position; - bool needFlushItems = false; + var needFlushItems = false; - TechLogItem item; while (!_cancellationToken.IsCancellationRequested) { - item = reader.ReadNextItem(_cancellationToken); + var item = reader.ReadNextItem(_cancellationToken); if (item == null) { @@ -187,37 +208,31 @@ namespace OneSTools.TechLog.Exporter.Core if (needFlushItems) { if (items.Count > 0) - Post(items.ToArray(), _writeBlock, _cancellationToken); + Post(items.ToArray(), _analyzerBlock, _cancellationToken); break; } - else - { - needFlushItems = true; - await Task.Delay(_settings.ReadingTimeout * 1000); - continue; - } - } - else - { - if (items.Count > 0) - Post(items.ToArray(), _writeBlock, _cancellationToken); - break; + needFlushItems = true; + await Task.Delay(_settings.ReadingTimeout * 1000, _cancellationToken); + continue; } + + if (items.Count > 0) + Post(items.ToArray(), _analyzerBlock, _cancellationToken); + + break; } - else - { - needFlushItems = false; - items.Add(item); + needFlushItems = false; - if (items.Count == items.Capacity) - { - Post(items.ToArray(), _writeBlock, _cancellationToken); - items.Clear(); - } - } + items.Add(item); + + if (items.Count != items.Capacity) + continue; + + Post(items.ToArray(), _analyzerBlock, _cancellationToken); + items.Clear(); } } catch (TaskCanceledException) { } @@ -228,7 +243,7 @@ namespace OneSTools.TechLog.Exporter.Core catch (Exception ex) { _logger.LogError(ex, "Failed to execute TechLogExporter"); - throw ex; + throw; } finally { diff --git a/OneSTools.TechLog.Exporter/Program.cs b/OneSTools.TechLog.Exporter/Program.cs index 66282fe..ee80df5 100644 --- a/OneSTools.TechLog.Exporter/Program.cs +++ b/OneSTools.TechLog.Exporter/Program.cs @@ -2,14 +2,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using OneSTools.TechLog.Exporter; using OneSTools.TechLog.Exporter.ClickHouse; using OneSTools.TechLog.Exporter.Core; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; namespace OneSTools.TechLog.Exporter { diff --git a/OneSTools.TechLog.Exporter/StorageType.cs b/OneSTools.TechLog.Exporter/StorageType.cs index 432fc9b..68a6a41 100644 --- a/OneSTools.TechLog.Exporter/StorageType.cs +++ b/OneSTools.TechLog.Exporter/StorageType.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OneSTools.TechLog.Exporter +namespace OneSTools.TechLog.Exporter { public enum StorageType { diff --git a/OneSTools.TechLog.Exporter/TechLogExporterService.cs b/OneSTools.TechLog.Exporter/TechLogExporterService.cs index 5e58583..c78a21c 100644 --- a/OneSTools.TechLog.Exporter/TechLogExporterService.cs +++ b/OneSTools.TechLog.Exporter/TechLogExporterService.cs @@ -1,14 +1,5 @@ -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using OneSTools.TechLog; -using OneSTools.TechLog.Exporter; -using OneSTools.TechLog.Exporter.ClickHouse; using OneSTools.TechLog.Exporter.Core; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/OneSTools.TechLog.Exporter/appsettings.Development.json b/OneSTools.TechLog.Exporter/appsettings.Development.json index 6f02a88..3135e0a 100644 --- a/OneSTools.TechLog.Exporter/appsettings.Development.json +++ b/OneSTools.TechLog.Exporter/appsettings.Development.json @@ -10,9 +10,9 @@ "StorageType": 1 }, "ClickHouse": { - "ConnectionString": "Host=172.19.158.128;Port=8123;Database=trprof83_techlog;Username=default;password=;" + "ConnectionString": "Host=172.19.149.61;Port=8123;Database=techlog_all_rds;Username=default;password=;" }, "Reader": { - "LogFolder": "E:\\techlog_all_s01" + "LogFolder": "E:\\techlog_all_rds" } -} +} \ No newline at end of file diff --git a/OneSTools.TechLog.Exporter/appsettings.json b/OneSTools.TechLog.Exporter/appsettings.json index 8983e0f..4f30a00 100644 --- a/OneSTools.TechLog.Exporter/appsettings.json +++ b/OneSTools.TechLog.Exporter/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } diff --git a/OneSTools.TechLog.sln b/OneSTools.TechLog.sln index 820ea78..ac43b07 100644 --- a/OneSTools.TechLog.sln +++ b/OneSTools.TechLog.sln @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.TechLog.Exporter. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.TechLog.Exporter", "OneSTools.TechLog.Exporter\OneSTools.TechLog.Exporter.csproj", "{860F1C21-5449-403B-AA90-B988B394E394}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneSTools.TechLog.Exporter.ClickHouse", "OneSTools.TechLog.Exporter.ClickHouse\OneSTools.TechLog.Exporter.ClickHouse.csproj", "{D2651363-59FF-42FE-892E-5A4E6F910EBB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.TechLog.Exporter.ClickHouse", "OneSTools.TechLog.Exporter.ClickHouse\OneSTools.TechLog.Exporter.ClickHouse.csproj", "{D2651363-59FF-42FE-892E-5A4E6F910EBB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/OneSTools.TechLog/AdditionalProperty.cs b/OneSTools.TechLog/AdditionalProperty.cs deleted file mode 100644 index 8ccccfa..0000000 --- a/OneSTools.TechLog/AdditionalProperty.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OneSTools.TechLog -{ - public enum AdditionalProperty - { - None = 0x1, - SqlHash = 0x2, - CleanSql = 0x3, - FirstContextLine = 0x4, - LastContextLine = 0x5, - All = 0xF - } -} diff --git a/OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs b/OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs new file mode 100644 index 0000000..add69bb --- /dev/null +++ b/OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +namespace OneSTools.TechLog +{ + public class DeadlockConnectionIntersectionsInfo + { + public ReadOnlyCollection Pairs { get; } + + public DeadlockConnectionIntersectionsInfo(string data) + { + var pairs = new List(); + + var pairsMatch = Regex.Matches(data, @"\d+ \d+.*?(?=(,\d+ \d+|$))", RegexOptions.ExplicitCapture); + + foreach (Match pairMatch in pairsMatch) + { + var pair = new DeadlockConnectionIntersectionsPair(pairMatch.Value); + pairs.Add(pair); + } + + Pairs = pairs.AsReadOnly(); + } + } +} diff --git a/OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs b/OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs new file mode 100644 index 0000000..d49ceeb --- /dev/null +++ b/OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OneSTools.TechLog +{ + public class DeadlockConnectionIntersectionsPair + { + public long Waiter { get; } + public long Locker { get; } + public string Region { get; } + public string BlockingMode { get; } + public List> FieldValues { get; } = new List>(); + + public DeadlockConnectionIntersectionsPair(string data) + { + Waiter = long.Parse(Regex.Match(data, @"^\d+", RegexOptions.ExplicitCapture).Value); + Locker = long.Parse(Regex.Match(data, @"(?<=^\d+) \d+", RegexOptions.ExplicitCapture).Value); + Region = Regex.Match(data, @"(?<=^\d+ \d+ )\w+\.\w+", RegexOptions.ExplicitCapture).Value; + BlockingMode = Regex.Match(data, @"(?<=^\d+ \d+ \w+\.\w+ )\w+", RegexOptions.ExplicitCapture).Value; + + var fields = Regex.Matches(data, @"\w+=.*?(?=( \w+=|$))", RegexOptions.ExplicitCapture); + + foreach (Match fieldMatch in fields) + { + var fieldData = fieldMatch.Value; + + var splitIndex = fieldData.IndexOf('='); + var field = fieldData[..splitIndex]; + var value = fieldData[(splitIndex + 1)..]; + + FieldValues.Add(new KeyValuePair(field, value.Trim('"'))); + } + } + } +} \ No newline at end of file diff --git a/OneSTools.TechLog/LocksInfo.cs b/OneSTools.TechLog/LocksInfo.cs new file mode 100644 index 0000000..dba379d --- /dev/null +++ b/OneSTools.TechLog/LocksInfo.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +namespace OneSTools.TechLog +{ + public class LocksInfo + { + public ReadOnlyCollection Regions { get; } + + public LocksInfo(string locks) + { + var regions = new List(); + + var regionsMatch = Regex.Matches(locks, @"\w+\.\w+.*?(?=(,\w+\.\w+|$))", RegexOptions.ExplicitCapture); + + foreach(Match regionMatch in regionsMatch) + { + var region = new LocksInfoRegion(regionMatch.Value); + regions.Add(region); + } + + Regions = regions.AsReadOnly(); + } + } +} \ No newline at end of file diff --git a/OneSTools.TechLog/LocksInfoRegion.cs b/OneSTools.TechLog/LocksInfoRegion.cs new file mode 100644 index 0000000..a32f615 --- /dev/null +++ b/OneSTools.TechLog/LocksInfoRegion.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OneSTools.TechLog +{ + public class LocksInfoRegion + { + public string Region { get; } + public string BlockingMode { get; } + public List<(string Field, string Values)> FieldValues { get; } = new List<(string, string)>(); + public string Hash { get; } + + public LocksInfoRegion(string locks) + { + Hash = TechLogItem.GetMd5Hash(locks); + + Region = Regex.Match(locks, @"^\w+\.\w+", RegexOptions.ExplicitCapture).Value; + BlockingMode = Regex.Match(locks, @"(?<=^\w+\.\w+ ).*?(?= )", RegexOptions.ExplicitCapture).Value; + + var fields = Regex.Matches(locks, @"\w+=.*?(?=( \w+=|$))", RegexOptions.ExplicitCapture); + + foreach (Match fieldMatch in fields) + { + var fieldData = fieldMatch.Value; + + var splitIndex = fieldData.IndexOf('='); + var field = fieldData[..splitIndex]; + var value = fieldData[(splitIndex + 1)..]; + + FieldValues.Add((field, value.Trim('"'))); + } + } + } +} \ No newline at end of file diff --git a/OneSTools.TechLog/LogReaderTimeoutException.cs b/OneSTools.TechLog/LogReaderTimeoutException.cs deleted file mode 100644 index ecab1f4..0000000 --- a/OneSTools.TechLog/LogReaderTimeoutException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace OneSTools.TechLog -{ - public class LogReaderTimeoutException : Exception - { - - } -} diff --git a/OneSTools.TechLog/OneSTools.TechLog.csproj b/OneSTools.TechLog/OneSTools.TechLog.csproj index 0812ce6..352628e 100644 --- a/OneSTools.TechLog/OneSTools.TechLog.csproj +++ b/OneSTools.TechLog/OneSTools.TechLog.csproj @@ -9,7 +9,7 @@ https://github.com/akpaevj/OneSTools.TechLog Akpaev Evgeny Библиотека для чтения и парсинга технологического журнала 1С - 2.1.4 + 2.1.5 8.0 onestools_icon_nuget.png diff --git a/OneSTools.TechLog/StreamReaderExtensions.cs b/OneSTools.TechLog/StreamReaderExtensions.cs index b1a86a8..c0e240c 100644 --- a/OneSTools.TechLog/StreamReaderExtensions.cs +++ b/OneSTools.TechLog/StreamReaderExtensions.cs @@ -1,27 +1,25 @@ using System; -using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Text; namespace OneSTools.TechLog { internal static class StreamReaderExtensions { - readonly static FieldInfo charPosField = typeof(StreamReader).GetField("_charPos", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); - readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("_byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); - readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("_charBuffer", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + static readonly FieldInfo charPosField = typeof(StreamReader).GetField("_charPos", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + static readonly FieldInfo byteLenField = typeof(StreamReader).GetField("_byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + static readonly FieldInfo charBufferField = typeof(StreamReader).GetField("_charBuffer", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); public static long GetPosition(this StreamReader reader) { // shift position back from BaseStream.Position by the number of bytes read // into internal buffer. - int byteLen = (int)byteLenField.GetValue(reader); + var byteLen = (int)byteLenField.GetValue(reader); var position = reader.BaseStream.Position - byteLen; // if we have consumed chars from the buffer we need to calculate how many // bytes they represent in the current encoding and add that to the position. - int charPos = (int)charPosField.GetValue(reader); + var charPos = (int)charPosField.GetValue(reader); if (charPos > 0) { var charBuffer = (char[])charBufferField.GetValue(reader); diff --git a/OneSTools.TechLog/TechLogFileReader.cs b/OneSTools.TechLog/TechLogFileReader.cs index a00be7d..2c92096 100644 --- a/OneSTools.TechLog/TechLogFileReader.cs +++ b/OneSTools.TechLog/TechLogFileReader.cs @@ -1,31 +1,24 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; -using System.Security.Cryptography; -using System.IO.MemoryMappedFiles; -using System.Linq; -using System.Collections.ObjectModel; namespace OneSTools.TechLog { public class TechLogFileReader : IDisposable { private readonly string _fileDateTime; - private readonly AdditionalProperty _additionalProperty; private FileStream _fileStream; private StreamReader _streamReader; private readonly StringBuilder _data = new StringBuilder(); private long _lastEventEndPosition = -1; - private bool disposedValue; + private bool _disposedValue; - public string FilePath { get; private set; } = ""; - public string FolderName { get; private set; } = ""; - public string FileName { get; private set; } = ""; + public string FilePath { get; } + public string FolderName { get; } + public string FileName { get; } public long Position { get @@ -40,14 +33,12 @@ namespace OneSTools.TechLog } } - public TechLogFileReader(string filePath, AdditionalProperty additionalProperty) + public TechLogFileReader(string filePath) { FilePath = filePath; FolderName = Directory.GetParent(FilePath).Name; FileName = Path.GetFileName(FilePath); - _additionalProperty = additionalProperty; - _fileDateTime = "20" + FileName.Substring(0, 2) + "-" + @@ -67,16 +58,9 @@ namespace OneSTools.TechLog if (rawItem == null) return null; - try - { - var parsed = ParseRawItem(rawItem.Trim(), cancellationToken); + var parsed = ParseRawItem(rawItem.Trim(), cancellationToken); - return parsed; - } - catch (Exception ex) - { - throw ex; - } + return parsed; } private ReadOnlySpan ReadRawItem(CancellationToken cancellationToken = default) @@ -92,27 +76,21 @@ namespace OneSTools.TechLog // This is the end of the event or the end of the stream if (line is null || _data.Length > 0 && Regex.IsMatch(line, @"^\d\d:\d\d\.", RegexOptions.Compiled)) break; - else - { - if (line.Length > 0) - _data.AppendLine(line); + if (line.Length > 0) + _data.AppendLine(line); - _lastEventEndPosition = Position; - } + _lastEventEndPosition = Position; } if (_data.Length == 0) return null; - else - { - var result = $"{_fileDateTime}:{_data}"; - _data.Clear(); + var result = $"{_fileDateTime}:{_data}"; + _data.Clear(); - if (line != null) - _data.AppendLine(line); + if (line != null) + _data.AppendLine(line); - return result.AsSpan(); - } + return result.AsSpan(); } private TechLogItem ParseRawItem(ReadOnlySpan rawItem, CancellationToken cancellationToken = default) @@ -129,23 +107,22 @@ namespace OneSTools.TechLog // set event end position no new line _lastEventEndPosition = Position; - var position = 0; - - var dtd = ReadNextPropertyWithoutName(rawItem, ','); - var dtdLength = dtd.Length; + var dtd = ReadNextPropertyWithoutName(rawItem); var dtEndIndex = dtd.LastIndexOf('-'); - item.DateTime = DateTime.Parse(dtd[0..dtEndIndex]); - position = dtEndIndex + 1; + item.DateTime = DateTime.Parse(dtd[..dtEndIndex]); + item.EndTicks = item.DateTime.Ticks; + var position = dtEndIndex + 1; - var duration = ReadNextPropertyWithoutName(rawItem[position..], ','); + var duration = ReadNextPropertyWithoutName(rawItem[position..]); position += duration.Length + 1; item.Duration = long.Parse(duration); + item.StartTicks = item.EndTicks - item.Duration * (TimeSpan.TicksPerMillisecond / 1000); - var eventName = ReadNextPropertyWithoutName(rawItem[position..], ','); + var eventName = ReadNextPropertyWithoutName(rawItem[position..]); position += eventName.Length + 1; item.EventName = eventName.ToString(); - var level = ReadNextPropertyWithoutName(rawItem[position..], ','); + var level = ReadNextPropertyWithoutName(rawItem[position..]); position += level.Length + 1; item.Level = int.Parse(level); @@ -163,31 +140,26 @@ namespace OneSTools.TechLog var propertyValue = ReadPropertyValue(rawItem[position..]); position += propertyValue.Length + 1; - propertyValue = propertyValue.Trim(new char[] { '\'', '"' }).Trim(); + propertyValue = propertyValue.Trim(new[] { '\'', '"' }).Trim(); if (!item.AllProperties.TryAdd(propertyName.ToString(), propertyValue.ToString())) - item.AllProperties.TryAdd(GetPropertyName(properties, propertyName, 0), propertyValue.ToString()); + item.AllProperties.TryAdd(GetPropertyName(properties, propertyName), propertyValue.ToString()); if (position >= rawItem.Length) break; } - SetAdditionalProperties(item.AllProperties); - return item; } - private ReadOnlySpan ReadPropertyName(ReadOnlySpan strData) + private static ReadOnlySpan ReadPropertyName(ReadOnlySpan strData) { var endIndex = strData.IndexOf('='); - if (endIndex == -1) - return ""; - - return strData[..endIndex]; + return endIndex == -1 ? "" : strData[..endIndex]; } - private ReadOnlySpan ReadPropertyValue(ReadOnlySpan strData) + private static ReadOnlySpan ReadPropertyValue(ReadOnlySpan strData) { var nextChar = strData[0]; @@ -217,20 +189,20 @@ namespace OneSTools.TechLog return strData[..endIndex]; } - private bool NeedAdditionalProperty(AdditionalProperty additionalProperty) - => (_additionalProperty & additionalProperty) == additionalProperty; - - private string GetPropertyName(Dictionary properties, ReadOnlySpan name, int number = 0) + private static string GetPropertyName(IReadOnlyDictionary properties, ReadOnlySpan name, int number = 0) { - var currentName = $"{name.ToString()}{number}"; + while (true) + { + var currentName = $"{name.ToString()}{number}"; - if (!properties.ContainsKey(currentName)) - return currentName; - else - return GetPropertyName(properties, name, number + 1); + if (!properties.ContainsKey(currentName)) + return currentName; + + number += 1; + } } - private ReadOnlySpan ReadNextPropertyWithoutName(ReadOnlySpan strData, char delimiter = ',') + private static ReadOnlySpan ReadNextPropertyWithoutName(ReadOnlySpan strData, char delimiter = ',') { var endPosition = strData.IndexOf(delimiter); var data = strData[..endPosition]; @@ -238,140 +210,22 @@ namespace OneSTools.TechLog return data; } - private void SetAdditionalProperties(Dictionary properties) - { - TrySetCleanSqlProperty(properties); - TrySetSqlHashProperty(properties); - TrySetFirstContextLineProperty(properties); - TrySetLastContextLineProperty(properties); - } - - private bool TrySetCleanSqlProperty(Dictionary properties) - { - if (NeedAdditionalProperty(AdditionalProperty.CleanSql) && properties.TryGetValue("Sql", out var sql)) - { - properties.Add("CleanSql", ClearSql(sql).ToString()); - - return true; - } - - return false; - } - - private ReadOnlySpan ClearSql(ReadOnlySpan data) - { - // Remove parameters - int startIndex = data.IndexOf("sp_executesql", StringComparison.OrdinalIgnoreCase); - - if (startIndex < 0) - startIndex = 0; - else - startIndex += 16; - - int e1 = data.IndexOf("', N'@P", StringComparison.OrdinalIgnoreCase); - if (e1 < 0) - e1 = data.Length; - - var e2 = data.IndexOf("p_0:", StringComparison.OrdinalIgnoreCase); - if (e2 < 0) - e2 = data.Length; - - var endIndex = Math.Min(e1, e2); - - // Remove temp table names, parameters and guids - var result = Regex.Replace(data[startIndex..endIndex].ToString(), @"(#tt\d+|@P\d+|\d{8}-\d{4}-\d{4}-\d{4}-\d{12})", "{RD}", RegexOptions.ExplicitCapture); - - return result; - } - - private bool TrySetSqlHashProperty(Dictionary properties) - { - bool needCalculateHash = NeedAdditionalProperty(AdditionalProperty.SqlHash); - - if (!properties.ContainsKey("CleanSql")) - needCalculateHash = TrySetCleanSqlProperty(properties); - - if (needCalculateHash && properties.TryGetValue("CleanSql", out var cleanedSql)) - properties.Add("SqlHash", GetSqlHash(cleanedSql)); - - return needCalculateHash; - } - - private string GetSqlHash(ReadOnlySpan cleanedSql) - { - using var cp = MD5.Create(); - var src = Encoding.UTF8.GetBytes(cleanedSql.ToString()); - var res = cp.ComputeHash(src); - - return BitConverter.ToString(res).Replace("-", ""); - } - - private bool TrySetFirstContextLineProperty(Dictionary properties) - { - if (NeedAdditionalProperty(AdditionalProperty.FirstContextLine) && properties.TryGetValue("Context", out var context)) - { - properties.Add("FirstContextLine", GetFirstContextLine(context).ToString()); - - return true; - } - - return false; - } - - private ReadOnlySpan GetFirstContextLine(ReadOnlySpan context) - { - var index = context.IndexOf('\n'); - - if (index > 0) - return context[0..index].Trim(); - else - return context; - } - - private bool TrySetLastContextLineProperty(Dictionary properties) - { - if (NeedAdditionalProperty(AdditionalProperty.LastContextLine) && properties.TryGetValue("Context", out var context)) - { - properties.Add("LastContextLine", GetLastContextLine(context).ToString()); - - return true; - } - - return false; - } - - private ReadOnlySpan GetLastContextLine(ReadOnlySpan context) - { - var index = context.LastIndexOf('\t'); - - if (index > 0) - return context[(index + 1)..].Trim(); - else - return context; - } - private void InitializeStream() { - if (_fileStream == null) - { - _fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); - _streamReader = new StreamReader(_fileStream); - } + if (_fileStream != null) + return; + + _fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + _streamReader = new StreamReader(_fileStream); } protected virtual void Dispose(bool disposing) { - if (!disposedValue) - { - if (disposing) - { - - } + if (_disposedValue) + return; - _streamReader?.Dispose(); - - disposedValue = true; - } + _streamReader?.Dispose(); + _disposedValue = true; } ~TechLogFileReader() diff --git a/OneSTools.TechLog/TechLogItem.cs b/OneSTools.TechLog/TechLogItem.cs index 476bead..73baf62 100644 --- a/OneSTools.TechLog/TechLogItem.cs +++ b/OneSTools.TechLog/TechLogItem.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; namespace OneSTools.TechLog { @@ -15,6 +18,9 @@ namespace OneSTools.TechLog /// public DateTime DateTime { get; internal set; } + public long StartTicks { get; internal set; } + public long EndTicks { get; internal set; } + /// /// The duration of the event (in microseconds) /// @@ -48,518 +54,552 @@ namespace OneSTools.TechLog /// /// A number of the thread /// - public string OSThread => AllProperties.GetValueOrDefault("OSThread", ""); + public string OSThread => AllProperties.GetValueOrDefault("OSThread", null); /// /// A name of the process /// - public string Process => AllProperties.GetValueOrDefault("process", ""); + public string Process => AllProperties.GetValueOrDefault("process", null); /// /// Время зависания процесса /// - public string AbandonedTimestamp => AllProperties.GetValueOrDefault("abandonedTimestamp", ""); + public string AbandonedTimestamp => AllProperties.GetValueOrDefault("abandonedTimestamp", null); /// /// Текстовое описание выполняемой операции во время загрузки конфигурации из файлов /// - public string Action => AllProperties.GetValueOrDefault("Action", ""); + public string Action => AllProperties.GetValueOrDefault("Action", null); /// /// Имя администратора кластера или центрального сервера /// - public string Admin => AllProperties.GetValueOrDefault("Admin", ""); + public string Admin => AllProperties.GetValueOrDefault("Admin", null); /// /// Имя администратора /// - public string Administrator => AllProperties.GetValueOrDefault("Administrator", ""); + public string Administrator => AllProperties.GetValueOrDefault("Administrator", null); /// /// Адрес текущего процесса агента сервера системы «1С:Предприятие» /// - public string AgentURL => AllProperties.GetValueOrDefault("agentURL", ""); + public string AgentURL => AllProperties.GetValueOrDefault("agentURL", null); - public string AlreadyLocked => AllProperties.GetValueOrDefault("AlreadyLocked", ""); + public string AlreadyLocked => AllProperties.GetValueOrDefault("AlreadyLocked", null); - public string AppID => AllProperties.GetValueOrDefault("AppID", ""); + public string AppID => AllProperties.GetValueOrDefault("AppID", null); - public string Appl => AllProperties.GetValueOrDefault("Appl", ""); + public string Appl => AllProperties.GetValueOrDefault("Appl", null); /// /// Уточнение требования назначения функциональности /// - public string ApplicationExt => AllProperties.GetValueOrDefault("ApplicationExt", ""); + public string ApplicationExt => AllProperties.GetValueOrDefault("ApplicationExt", null); /// /// Количество попыток установки соединения с процессом, завершившихся ошибкой /// - public long Attempts => long.Parse(AllProperties.GetValueOrDefault("Attempts", "-1")); + public long? Attempts => GetLongOrNull("Attempts"); /// /// Среднее количество исключений за последние 5 минут по другим процессам /// - public long AvgExceptions => long.Parse(AllProperties.GetValueOrDefault("AvgExceptions", "-1")); + public long? AvgExceptions => GetLongOrNull("AvgExceptions"); /// /// Значение показателя Доступная память в момент вывода в технологический журнал /// - public long AvMem => long.Parse(AllProperties.GetValueOrDefault("AvMem", "-1")); + public long? AvMem => GetLongOrNull("AvMem"); /// /// Формирование индекса полнотекстового поиска выполнялось в фоновом процессе /// - public string BackgroundJobCreated => AllProperties.GetValueOrDefault("BackgroundJobCreated", ""); + public string BackgroundJobCreated => AllProperties.GetValueOrDefault("BackgroundJobCreated", null); /// /// Размер в байтах тела запроса/ответа /// - public long Body => long.Parse(AllProperties.GetValueOrDefault("Body", "-1")); + public long? Body => GetLongOrNull("Body"); - public int CallID => int.Parse(AllProperties.GetValueOrDefault("CallID", "-1")); + public int? CallID => GetIntOrNull("CallID"); /// /// Количество обращений клиентского приложения к серверному приложению через TCP /// - public int Calls => int.Parse(AllProperties.GetValueOrDefault("Calls", "-1")); + public int? Calls => GetIntOrNull("Calls"); /// /// Описание проверяемого сертификата /// - public string Certificate => AllProperties.GetValueOrDefault("certificate", ""); + public string Certificate => AllProperties.GetValueOrDefault("certificate", null); /// /// Имя класса, в котором сгенерировано событие /// - public string Class => AllProperties.GetValueOrDefault("Class", ""); + public string Class => AllProperties.GetValueOrDefault("Class", null); - public int CallWait => int.Parse(AllProperties.GetValueOrDefault("callWait", "-1")); + public int? CallWait => GetIntOrNull("callWait"); /// /// Очищенный текст SQL запроса /// - public string CleanSql => AllProperties.GetValueOrDefault("CleanSql", ""); + public string CleanSql => GetCleanSql(Sql); - public string ClientComputerName => AllProperties.GetValueOrDefault("ClientComputerName", ""); + public string ClientComputerName => AllProperties.GetValueOrDefault("ClientComputerName", null); - public int ClientID => int.Parse(AllProperties.GetValueOrDefault("ClientID", "-1")); + public int? ClientID => GetIntOrNull("ClientID"); /// /// Номер основного порта кластера серверов /// - public string Cluster => AllProperties.GetValueOrDefault("Cluster", ""); + public string Cluster => AllProperties.GetValueOrDefault("Cluster", null); - public string ClusterID => AllProperties.GetValueOrDefault("ClusterID", ""); + public string ClusterID => AllProperties.GetValueOrDefault("ClusterID", null); - public string ConnectionID => AllProperties.GetValueOrDefault("connectionID", ""); + public string ConnectionID => AllProperties.GetValueOrDefault("connectionID", null); /// /// Имя компоненты платформы, в которой сгенерировано событие /// - public string Component => AllProperties.GetValueOrDefault("Component", ""); + public string Component => AllProperties.GetValueOrDefault("Component", null); /// /// Номер соединения с информационной базой /// - public long Connection => long.Parse(AllProperties.GetValueOrDefault("Connection", "-1")); + public long? Connection => GetLongOrNull("Connection"); /// /// Количество соединений, которым не хватило рабочих процессов /// - public long Connections => long.Parse(AllProperties.GetValueOrDefault("Connections", "-1")); + public long? Connections => GetLongOrNull("Connections"); /// /// Установленное максимальное количество соединений на один рабочий процесс /// - public long ConnLimit => long.Parse(AllProperties.GetValueOrDefault("ConnLimit", "-1")); + public long? ConnLimit => GetLongOrNull("ConnLimit"); /// /// Контекст исполнения /// - public string Context => AllProperties.GetValueOrDefault("Context", ""); + public string Context + { + get => AllProperties.GetValueOrDefault("Context", null); + set => AllProperties["Context"] = value; + } /// /// Общий размер скопированных значений при сборке мусора /// - public long CopyBytes => long.Parse(AllProperties.GetValueOrDefault("CopyBytes", "-1")); + public long? CopyBytes => GetLongOrNull("CopyBytes"); /// /// Длительность вызова в микросекундах /// - public long CpuTime => long.Parse(AllProperties.GetValueOrDefault("CpuTime", "-1")); + public long? CpuTime => GetLongOrNull("CpuTime"); - public int CreateDump => int.Parse(AllProperties.GetValueOrDefault("createDump", "-1")); + public int? CreateDump => GetIntOrNull("createDump"); - public string Cycles => AllProperties.GetValueOrDefault("Cycles", ""); + public string Cycles => AllProperties.GetValueOrDefault("Cycles", null); /// /// Количество исключений в процессе за последние 5 минут /// - public long CurExceptions => long.Parse(AllProperties.GetValueOrDefault("CurExceptions", "-1")); + public long? CurExceptions => GetLongOrNull("CurExceptions"); /// /// Путь к используемой базе данных /// - public string Database => AllProperties.GetValueOrDefault("DataBase", ""); + public string Database => AllProperties.GetValueOrDefault("DataBase", null); /// /// Идентификатор соединения с СУБД внешнего источника данных /// - public string DBConnID => AllProperties.GetValueOrDefault("DBConnID", ""); + public string DBConnID => AllProperties.GetValueOrDefault("DBConnID", null); /// /// Строка соединения с внешним источником данных /// - public string DBConnStr => AllProperties.GetValueOrDefault("DBConnStr", ""); + public string DBConnStr => AllProperties.GetValueOrDefault("DBConnStr", null); /// /// Имя используемой копии базы данных /// - public string DBCopy => AllProperties.GetValueOrDefault("DBCopy", ""); + public string DBCopy => AllProperties.GetValueOrDefault("DBCopy", null); /// /// Имя СУБД, используемой для выполнения операции, которая повлекла формирование данного события технологического журнала /// - public string DBMS => AllProperties.GetValueOrDefault("DBMS", ""); + public string DBMS => AllProperties.GetValueOrDefault("DBMS", null); /// /// Строковое представление идентификатора соединения сервера системы «1С:Предприятие» с сервером баз данных в терминах сервера баз данных /// - public string Dbpid => AllProperties.GetValueOrDefault("dbpid", ""); + public string Dbpid => AllProperties.GetValueOrDefault("dbpid", null); /// /// Имя пользователя СУБД внешнего источника данных /// - public string DBUsr => AllProperties.GetValueOrDefault("DBUsr", ""); + public string DBUsr => AllProperties.GetValueOrDefault("DBUsr", null); /// /// Список пар транзакций, образующих взаимную блокировку /// - public string DeadlockConnectionIntersections => AllProperties.GetValueOrDefault("DeadlockConnectionIntersections", ""); + public string DeadlockConnectionIntersections => AllProperties.GetValueOrDefault("DeadlockConnectionIntersections", null); + + public DeadlockConnectionIntersectionsInfo DeadlockConnectionIntersectionsInfo + => string.IsNullOrEmpty(DeadlockConnectionIntersections) ? null : new DeadlockConnectionIntersectionsInfo(DeadlockConnectionIntersections); /// /// Пояснения к программному исключению /// - public string Descr => AllProperties.GetValueOrDefault("Descr", ""); + public string Descr => AllProperties.GetValueOrDefault("Descr", null); /// /// Текст, поясняющий выполняемое действие /// - public string Description => AllProperties.GetValueOrDefault("description", ""); + public string Description => AllProperties.GetValueOrDefault("description", null); /// /// Назначенный адрес рабочего процесса /// - public string DstAddr => AllProperties.GetValueOrDefault("DstAddr", ""); + public string DstAddr => AllProperties.GetValueOrDefault("DstAddr", null); /// /// Уникальный идентификатор назначенного рабочего процесса /// - public string DstId => AllProperties.GetValueOrDefault("DstId", ""); + public string DstId => AllProperties.GetValueOrDefault("DstId", null); /// /// Системный идентификатор назначенного рабочего процесса /// - public string DstPid => AllProperties.GetValueOrDefault("DstPid", ""); + public string DstPid => AllProperties.GetValueOrDefault("DstPid", null); /// /// Назначенное имя рабочего сервера /// - public string DstSrv => AllProperties.GetValueOrDefault("DstSrv", ""); + public string DstSrv => AllProperties.GetValueOrDefault("DstSrv", null); - public string DstClientID => AllProperties.GetValueOrDefault("DstClientID", ""); + public string DstClientID => AllProperties.GetValueOrDefault("DstClientID", null); - public long Durationus => long.Parse(AllProperties.GetValueOrDefault("Durationus", "-1")); + public int? Err => GetIntOrNull("Err"); - public int Err => int.Parse(AllProperties.GetValueOrDefault("Err", "-1")); + public string Exception => AllProperties.GetValueOrDefault("Exception", null); - public string Exception => AllProperties.GetValueOrDefault("Exception", ""); + public int? ExpirationTimeout => GetIntOrNull("expirationTimeout"); - public int ExpirationTimeout => int.Parse(AllProperties.GetValueOrDefault("expirationTimeout", "-1")); + public int? FailedJobsCount => GetIntOrNull("FailedJobsCount"); - public int FailedJobsCount => int.Parse(AllProperties.GetValueOrDefault("FailedJobsCount", "-1")); + public string Finish => AllProperties.GetValueOrDefault("Finish", null); - public string Finish => AllProperties.GetValueOrDefault("Finish", ""); - - public string First => AllProperties.GetValueOrDefault("first", ""); + public string First => AllProperties.GetValueOrDefault("first", null); /// /// Первая строка контекста исполнения /// - public string FirstContextLine => AllProperties.GetValueOrDefault("FirstContextLine", ""); + public string FirstContextLine + { + get + { + if (string.IsNullOrEmpty(Context)) + return ""; - public string Func => AllProperties.GetValueOrDefault("Func", ""); + var index = Context.IndexOf('\n'); - public string Headers => AllProperties.GetValueOrDefault("Headers", ""); + return index > 0 ? Context[..index].Trim() : Context; + } + } - public string Host => AllProperties.GetValueOrDefault("Host", ""); + public string Func => AllProperties.GetValueOrDefault("Func", null); - public string HResultNC2012 => AllProperties.GetValueOrDefault("hResultNC2012", ""); + public string Headers => AllProperties.GetValueOrDefault("Headers", null); - public string Ib => AllProperties.GetValueOrDefault("IB", ""); + public string Host => AllProperties.GetValueOrDefault("Host", null); - public string ID => AllProperties.GetValueOrDefault("ID", ""); + public string HResultNC2012 => AllProperties.GetValueOrDefault("hResultNC2012", null); + + public string Ib => AllProperties.GetValueOrDefault("IB", null); + + public string ID => AllProperties.GetValueOrDefault("ID", null); /// /// Имя передаваемого интерфейса, метод которого вызывается удаленно /// - public string IName => AllProperties.GetValueOrDefault("IName", ""); + public string IName => AllProperties.GetValueOrDefault("IName", null); /// /// Количество данных, прочитанных с диска за время вызова (в байтах) /// - public long InBytes => long.Parse(AllProperties.GetValueOrDefault("InBytes", "-1")); + public long? InBytes => GetLongOrNull("InBytes"); - public string InfoBaseID => AllProperties.GetValueOrDefault("InfoBaseID", ""); + public string InfoBaseID => AllProperties.GetValueOrDefault("InfoBaseID", null); - public string Interface => AllProperties.GetValueOrDefault("Interface", ""); + public string Interface => AllProperties.GetValueOrDefault("Interface", null); /// /// Последняя строка контекста исполнения /// - public string LastContextLine => AllProperties.GetValueOrDefault("LastContextLine", ""); + public string LastContextLine + { + get + { + if (string.IsNullOrEmpty(Context)) + return ""; + else + { + var index = Context.LastIndexOf('\t'); + + if (index > 0) + return Context[(index + 1)..].Trim(); + else + return Context; + } + } + } /// /// Поток является источником блокировки /// - public string Lka => AllProperties.GetValueOrDefault("lka", ""); + public string Lka => AllProperties.GetValueOrDefault("lka", null); /// /// Поток является жертвой блокировки /// - public string Lkp => AllProperties.GetValueOrDefault("lkp", ""); + public string Lkp => AllProperties.GetValueOrDefault("lkp", null); /// /// Номер запроса к СУБД, «кто кого заблокировал» (только для потока-жертвы блокировки). Например, ‘423’ /// - public string Lkpid => AllProperties.GetValueOrDefault("lkpid", ""); + public string Lkpid => AllProperties.GetValueOrDefault("lkpid", null); /// /// Cписок номеров запросов к СУБД, «кто кого заблокировал» (только для потока-источника блокировки). Например, ‘271,273,274’ /// - public string Lkaid => AllProperties.GetValueOrDefault("lkaid", ""); + public string Lkaid => AllProperties.GetValueOrDefault("lkaid", null); /// /// Массив значений из свойства lkaid /// - public int[] LkaidArray => Lkaid != "" ? Lkaid.Split(',').Select(c => int.Parse(c)).ToArray() : default; + public int[] LkaidArray => Lkaid != "" ? Lkaid.Split(',').Select(int.Parse).ToArray() : default; /// /// Номер соединения источника блокировки, если поток является жертвой, например, ‘23’ /// - public string Lksrc => AllProperties.GetValueOrDefault("lksrc", ""); + public string Lksrc => AllProperties.GetValueOrDefault("lksrc", null); /// /// Время в секундах, прошедшее с момента обнаружения, что поток является жертвой. Например: ‘15’ /// - public string Lkpto => AllProperties.GetValueOrDefault("lkpto", ""); + public string Lkpto => AllProperties.GetValueOrDefault("lkpto", null); /// /// Время в секундах, прошедшее с момента обнаружения, что поток является источником блокировок. Например, ‘21’ /// - public string Lkato => AllProperties.GetValueOrDefault("lkato", ""); + public string Lkato => AllProperties.GetValueOrDefault("lkato", null); - public string Locks => AllProperties.GetValueOrDefault("Locks", ""); + public string Locks => AllProperties.GetValueOrDefault("Locks", null); - public int LogOnly => int.Parse(AllProperties.GetValueOrDefault("logOnly", "-1")); + public LocksInfo LocksInfo => string.IsNullOrEmpty(Locks) ? null : new LocksInfo(Locks); - public long Memory => long.Parse(AllProperties.GetValueOrDefault("Memory", "-1")); + public int? LogOnly => GetIntOrNull("logOnly"); - public long MemoryPeak => long.Parse(AllProperties.GetValueOrDefault("MemoryPeak", "-1")); + public long? Memory => GetLongOrNull("Memory"); - public string Method => AllProperties.GetValueOrDefault("Method", ""); + public long? MemoryPeak => GetLongOrNull("MemoryPeak"); - public int MinDataId => int.Parse(AllProperties.GetValueOrDefault("MinDataId", "-1")); + public string Method => AllProperties.GetValueOrDefault("Method", null); + + public int? MinDataId => GetIntOrNull("MinDataId"); /// /// Имя удаленно вызываемого метода /// - public string MName => AllProperties.GetValueOrDefault("MName", ""); + public string MName => AllProperties.GetValueOrDefault("MName", null); - public string Module => AllProperties.GetValueOrDefault("Module", ""); + public string Module => AllProperties.GetValueOrDefault("Module", null); - public string ModuleName => AllProperties.GetValueOrDefault("ModuleName", ""); + public string ModuleName => AllProperties.GetValueOrDefault("ModuleName", null); - public string Name => AllProperties.GetValueOrDefault("Name", ""); + public string Name => AllProperties.GetValueOrDefault("Name", null); - public int Nmb => int.Parse(AllProperties.GetValueOrDefault("Nmb", "-1")); + public int? Nmb => GetIntOrNull("Nmb"); /// /// Количество данных, записанных на диск за время вызова (в байтах) /// - public long OutBytes => long.Parse(AllProperties.GetValueOrDefault("OutBytes", "-1")); + public long? OutBytes => GetLongOrNull("OutBytes"); - public string Phrase => AllProperties.GetValueOrDefault("Phrase", ""); + public string Phrase => AllProperties.GetValueOrDefault("Phrase", null); - public int Pid => int.Parse(AllProperties.GetValueOrDefault("Pid", "-1")); + public int? Pid => GetIntOrNull("Pid"); /// /// План запроса, содержащегося в свойстве Sql /// - public string PlanSQLText => AllProperties.GetValueOrDefault("planSQLText", ""); + public string PlanSQLText => AllProperties.GetValueOrDefault("planSQLText", null); /// /// Номер основного сетевого порта процесса /// - public int Port => int.Parse(AllProperties.GetValueOrDefault("Port", "-1")); + public int? Port => GetIntOrNull("Port"); /// /// Имя серверного контекста, который обычно совпадает с именем информационной базы /// - public string PProcessName => AllProperties.GetValueOrDefault("p:processName", ""); + public string PProcessName => AllProperties.GetValueOrDefault("p:processName", null); - public string Prm => AllProperties.GetValueOrDefault("Prm", ""); + public string Prm => AllProperties.GetValueOrDefault("Prm", null); - public string ProcedureName => AllProperties.GetValueOrDefault("ProcedureName", ""); + public string ProcedureName => AllProperties.GetValueOrDefault("ProcedureName", null); - public string ProcessID => AllProperties.GetValueOrDefault("ProcessID", ""); + public string ProcessID => AllProperties.GetValueOrDefault("ProcessID", null); /// /// Наименование процесса /// - public string ProcessName => AllProperties.GetValueOrDefault("ProcessName", ""); + public string ProcessName => AllProperties.GetValueOrDefault("ProcessName", null); /// /// Адрес процесса сервера системы «1С:Предприятие», к которому относится событие /// - public string ProcURL => AllProperties.GetValueOrDefault("procURL", ""); + public string ProcURL => AllProperties.GetValueOrDefault("procURL", null); - public int Protected => int.Parse(AllProperties.GetValueOrDefault("Protected", "-1")); + public int? Protected => GetIntOrNull("Protected"); /// /// Текст запроса на встроенном языке, при выполнении которого обнаружилось значение NULL в поле, для которого такое значение недопустимо /// - public string Query => AllProperties.GetValueOrDefault("Query", ""); + public string Query => AllProperties.GetValueOrDefault("Query", null); /// /// Перечень полей запроса, в которых обнаружены значения NULL /// - public string QueryFields => AllProperties.GetValueOrDefault("QueryFields", ""); + public string QueryFields => AllProperties.GetValueOrDefault("QueryFields", null); - public string Ranges => AllProperties.GetValueOrDefault("Ranges", ""); + public string Ranges => AllProperties.GetValueOrDefault("Ranges", null); /// /// Причина недоступности рабочего процесса /// - public string Reason => AllProperties.GetValueOrDefault("Reason", ""); + public string Reason => AllProperties.GetValueOrDefault("Reason", null); /// /// Имена пространств управляемых транзакционных блокировок /// - public string Regions => AllProperties.GetValueOrDefault("Regions", ""); + public string Regions => AllProperties.GetValueOrDefault("Regions", null); - public string Res => AllProperties.GetValueOrDefault("res", ""); + public string Res => AllProperties.GetValueOrDefault("res", null); - public string Result => AllProperties.GetValueOrDefault("Result", ""); + public string Result => AllProperties.GetValueOrDefault("Result", null); - public string RetExcp => AllProperties.GetValueOrDefault("RetExcp", ""); + public string RetExcp => AllProperties.GetValueOrDefault("RetExcp", null); /// /// Количество полученных записей базы данных /// - public int Rows => int.Parse(AllProperties.GetValueOrDefault("Rows", "-1")); + public int? Rows => GetIntOrNull("Rows"); /// /// Количество измененных записей базы данных /// - public int RowsAffected => int.Parse(AllProperties.GetValueOrDefault("RowsAffected", "-1")); + public int? RowsAffected => GetIntOrNull("RowsAffected"); /// /// Режим запуска процесса (приложение или сервис) /// - public string RunAs => AllProperties.GetValueOrDefault("RunAs", ""); + public string RunAs => AllProperties.GetValueOrDefault("RunAs", null); - public long SafeCallMemoryLimit => long.Parse(AllProperties.GetValueOrDefault("SafeCallMemoryLimit", "-1")); + public long? SafeCallMemoryLimit => GetLongOrNull("SafeCallMemoryLimit"); /// /// Текст запроса на встроенном языке модели базы данных /// - public string Sdbl => AllProperties.GetValueOrDefault("Sdbl", ""); + public string Sdbl => AllProperties.GetValueOrDefault("Sdbl", null); /// /// Имя рабочего сервера /// - public string ServerComputerName => AllProperties.GetValueOrDefault("ServerComputerName", ""); + public string ServerComputerName => AllProperties.GetValueOrDefault("ServerComputerName", null); - public string ServerID => AllProperties.GetValueOrDefault("ServerID", ""); + public string ServerID => AllProperties.GetValueOrDefault("ServerID", null); - public string ServerName => AllProperties.GetValueOrDefault("ServerName", ""); + public string ServerName => AllProperties.GetValueOrDefault("ServerName", null); /// /// Номер сеанса, назначенный текущему потоку. Если текущему потоку не назначен никакой сеанс, то свойство не добавляется /// - public string SessionID => AllProperties.GetValueOrDefault("SessionID", ""); + public string SessionID => AllProperties.GetValueOrDefault("SessionID", null); /// /// Текст оператора SQL /// - public string Sql => AllProperties.GetValueOrDefault("Sql", ""); + public string Sql => AllProperties.GetValueOrDefault("Sql", null); /// /// MD5 хеш значения свойства CleanSql /// - public string SqlHash => AllProperties.GetValueOrDefault("SqlHash", ""); + public string SqlHash => GetMd5Hash(CleanSql); - public string SrcName => AllProperties.GetValueOrDefault("SrcName", ""); + public string SrcName => AllProperties.GetValueOrDefault("SrcName", null); - public string SrcProcessName => AllProperties.GetValueOrDefault("SrcProcessName", ""); + public string SrcProcessName => AllProperties.GetValueOrDefault("SrcProcessName", null); - public string State => AllProperties.GetValueOrDefault("State", ""); + public string State => AllProperties.GetValueOrDefault("State", null); /// /// Код состояния HTTP /// - public int Status => int.Parse(AllProperties.GetValueOrDefault("Status", "-1")); + public int? Status => GetIntOrNull("Status"); - public int SyncPort => int.Parse(AllProperties.GetValueOrDefault("SyncPort", "-1")); + public int? SyncPort => GetIntOrNull("SyncPort"); /// /// Объем занятой процессом динамической памяти на момент вывода события MEM (в байтах) /// - public long Sz => long.Parse(AllProperties.GetValueOrDefault("Sz", "-1")); + public long? Sz => GetLongOrNull("Sz"); /// /// Изменение объема динамической памяти, занятой процессом, с момента вывода предыдущего события MEM (в байтах) /// - public long Szd => long.Parse(AllProperties.GetValueOrDefault("Szd", "-1")); + public long? Szd => GetLongOrNull("Szd"); - public string TableName => AllProperties.GetValueOrDefault("tableName", ""); + public string TableName => AllProperties.GetValueOrDefault("tableName", null); /// /// Идентификатор клиентской программы /// - public string TApplicationName => AllProperties.GetValueOrDefault("t:applicationName", ""); + public string TApplicationName => AllProperties.GetValueOrDefault("t:applicationName", null); /// /// Идентификатор соединения с клиентом по TCP /// - public int TClientID => int.Parse(AllProperties.GetValueOrDefault("t:clientID", "-1")); + public int? TClientID => GetIntOrNull("t:clientID"); /// /// Имя клиентского компьютера /// - public string TComputerName => AllProperties.GetValueOrDefault("t:computerName", ""); + public string TComputerName => AllProperties.GetValueOrDefault("t:computerName", null); /// /// Идентификатор соединения с информационной базой /// - public int TConnectID => int.Parse(AllProperties.GetValueOrDefault("t:connectID", "-1")); + public int? TConnectID => GetIntOrNull("t:connectID"); /// /// Время вывода записи в технологический журнал /// **Для события ATTN содержит имя серверного процесса: rmngr или rphost /// - public string Time => AllProperties.GetValueOrDefault("Time", ""); + public string Time => AllProperties.GetValueOrDefault("Time", null); - public int TotalJobsCount => int.Parse(AllProperties.GetValueOrDefault("TotalJobsCount", "-1")); + public int? TotalJobsCount => GetIntOrNull("TotalJobsCount"); /// /// Идентификатор активности транзакции на момент начала события: /// 0 ‑ транзакция не была открыта; /// 1 ‑ транзакция была открыта. /// - public int Trans => int.Parse(AllProperties.GetValueOrDefault("Trans", "-1")); + public int? Trans => GetIntOrNull("Trans"); - public int TTmpConnectID => int.Parse(AllProperties.GetValueOrDefault("t:tmpConnectID", "-1")); + public int? TTmpConnectID => GetIntOrNull("t:tmpConnectID"); /// /// Текст информационного сообщения @@ -571,31 +611,95 @@ namespace OneSTools.TechLog if (AllProperties.TryGetValue("Txt", out var txt)) return txt; else - return AllProperties.GetValueOrDefault("txt", ""); + return AllProperties.GetValueOrDefault("txt", null); } } /// /// Ресурс, к которому производится обращение /// - public string URI => AllProperties.GetValueOrDefault("URI", ""); + public string URI => AllProperties.GetValueOrDefault("URI", null); - public string UserName => AllProperties.GetValueOrDefault("UserName", ""); + public string UserName => AllProperties.GetValueOrDefault("UserName", null); /// /// Размер используемого места в хранилище, в байтах /// - public long UsedSize => long.Parse(AllProperties.GetValueOrDefault("UsedSize", "-1")); + public long? UsedSize => GetLongOrNull("UsedSize"); /// /// Имя пользователя информационной базы (если в информационной базе не определены пользователи, это свойство будет иметь значение DefUser). /// Значение свойства берется из назначенного сеанса. /// - public string Usr => AllProperties.GetValueOrDefault("Usr", ""); + public string Usr => AllProperties.GetValueOrDefault("Usr", null); /// /// Список соединений, с которыми идет столкновение по управляемым транзакционным блокировкам /// - public string WaitConnections => AllProperties.GetValueOrDefault("WaitConnections", ""); + public string WaitConnections => AllProperties.GetValueOrDefault("WaitConnections", null); + + private long? GetLongOrNull(string propertyName) + { + if (AllProperties.TryGetValue(propertyName, out var value)) + return long.Parse(value); + else + return null; + } + + private int? GetIntOrNull(string propertyName) + { + if (AllProperties.TryGetValue(propertyName, out var value)) + return int.Parse(value); + else + return null; + } + + public static string GetMd5Hash(string str) + { + if (string.IsNullOrEmpty(str)) + return ""; + + using var cp = MD5.Create(); + var src = Encoding.UTF8.GetBytes(str); + var res = cp.ComputeHash(src); + + return BitConverter.ToString(res).Replace("-", null); + } + + private string GetCleanSql(string data) + { + if (string.IsNullOrEmpty(data)) + return ""; + else + { + // Remove parameters + int startIndex = data.IndexOf("sp_executesql", StringComparison.OrdinalIgnoreCase); + + if (startIndex < 0) + startIndex = 0; + else + startIndex += 16; + + int e1 = data.IndexOf("', N'@P", StringComparison.OrdinalIgnoreCase); + if (e1 < 0) + e1 = data.Length; + + var e2 = data.IndexOf("p_0:", StringComparison.OrdinalIgnoreCase); + if (e2 < 0) + e2 = data.Length; + + var endIndex = Math.Min(e1, e2); + + // Remove temp table names, parameters and guids + var result = Regex.Replace(data[startIndex..endIndex], @"(#tt\d+|@P\d+|\d{8}-\d{4}-\d{4}-\d{4}-\d{12})", "{RD}", RegexOptions.ExplicitCapture); + + return result; + } + } + + public override string ToString() + { + return EventName; + } } } \ No newline at end of file