1
0
mirror of https://github.com/akpaevj/OneSTools.TechLog.git synced 2025-02-16 18:34:27 +02:00

Фиксация промежуточной работы

This commit is contained in:
Акпаев Евгений Александрович 2021-01-28 00:44:43 +03:00
parent 5957aa4556
commit 6a49a7d672
22 changed files with 580 additions and 617 deletions

View File

@ -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<ClickHouseStorage> _logger;
private readonly string _connectionString;
private readonly string _databaseName;
private readonly ClickHouseConnection _connection;
public ClickHouseStorage(ILogger<ClickHouseStorage> 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<ClickHouseStorage> 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<long> 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<object[]> entities, CancellationToken cancellationToken = default)
private async Task WriteBulkAsync(string tableName, IReadOnlyCollection<object[]> 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()

View File

@ -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 [остаток строки не уместился]&quot;;.
/// Sq [остаток строки не уместился]&quot;;.
/// </summary>
internal static string CreateItemsTable {
get {

View File

@ -121,9 +121,12 @@
<value>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)</value>
ORDER BY (DateTime, EndTicks, EventName)</value>
<comment>Создание таблицы для событий EXCP</comment>
</data>
<data name="CreateLastPositionsTable" xml:space="preserve">

View File

@ -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<long> GetLastPositionAsync(string folder, string file, CancellationToken cancellationToken = default);
Task WriteItemsAsync(TechLogItem[] items, CancellationToken cancellationToken = default);
Task WriteItemsAsync(TechLogItem[] items);
}
}

View File

@ -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<TechLogItem> _victims = new List<TechLogItem>();
public TechLogItem Source { get; private set; }
public ReadOnlyCollection<TechLogItem> 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<string, TechLogItem> _deadlockSources = new Dictionary<string, TechLogItem>();
private readonly Dictionary<string, TechLogItem> _unknownVictims = new Dictionary<string, TechLogItem>();
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)
{
}
}
}

View File

@ -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<string, List<string>>();
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<string>();
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<string>();
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<string> _beingReadFiles = new HashSet<string>();
private ActionBlock<TechLogItem[]> _writeBlock;
private TransformBlock<TechLogItem[], TechLogItem[]> _analyzerBlock;
private ActionBlock<string> _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<TechLogExporter> 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<TechLogItem[]>(WriteItems, writeBlockOptions);
var analyzerBlockOptions = new ExecutionDataflowBlockOptions()
{
CancellationToken = cancellationToken,
BoundedCapacity = _settings.BatchFactor
};
_analyzerBlock = new TransformBlock<TechLogItem[], TechLogItem[]>(AnalyzeItems, analyzerBlockOptions);
_analyzerBlock.LinkTo(_writeBlock, new DataflowLinkOptions { PropagateCompletion = true });
var readBlockOptions = new ExecutionDataflowBlockOptions()
{
CancellationToken = cancellationToken,
BoundedCapacity = DataflowBlockOptions.Unbounded
};
_readBlock = new ActionBlock<string>(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<TechLogItem>(_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
{

View File

@ -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
{

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OneSTools.TechLog.Exporter
namespace OneSTools.TechLog.Exporter
{
public enum StorageType
{

View File

@ -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;

View File

@ -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"
}
}
}

View File

@ -1,7 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Default": "Debug",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}

View File

@ -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

View File

@ -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
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
namespace OneSTools.TechLog
{
public class DeadlockConnectionIntersectionsInfo
{
public ReadOnlyCollection<DeadlockConnectionIntersectionsPair> Pairs { get; }
public DeadlockConnectionIntersectionsInfo(string data)
{
var pairs = new List<DeadlockConnectionIntersectionsPair>();
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();
}
}
}

View File

@ -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<KeyValuePair<string, string>> FieldValues { get; } = new List<KeyValuePair<string, string>>();
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<string, string>(field, value.Trim('"')));
}
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
namespace OneSTools.TechLog
{
public class LocksInfo
{
public ReadOnlyCollection<LocksInfoRegion> Regions { get; }
public LocksInfo(string locks)
{
var regions = new List<LocksInfoRegion>();
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();
}
}
}

View File

@ -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('"')));
}
}
}
}

View File

@ -1,9 +0,0 @@
using System;
namespace OneSTools.TechLog
{
public class LogReaderTimeoutException : Exception
{
}
}

View File

@ -9,7 +9,7 @@
<PackageProjectUrl>https://github.com/akpaevj/OneSTools.TechLog</PackageProjectUrl>
<Copyright>Akpaev Evgeny</Copyright>
<Description>Библиотека для чтения и парсинга технологического журнала 1С</Description>
<Version>2.1.4</Version>
<Version>2.1.5</Version>
<LangVersion>8.0</LangVersion>
<PackageIcon>onestools_icon_nuget.png</PackageIcon>
<PackageLicenseExpression></PackageLicenseExpression>

View File

@ -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);

View File

@ -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<char> 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<char> 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<char> ReadPropertyName(ReadOnlySpan<char> strData)
private static ReadOnlySpan<char> ReadPropertyName(ReadOnlySpan<char> strData)
{
var endIndex = strData.IndexOf('=');
if (endIndex == -1)
return "";
return strData[..endIndex];
return endIndex == -1 ? "" : strData[..endIndex];
}
private ReadOnlySpan<char> ReadPropertyValue(ReadOnlySpan<char> strData)
private static ReadOnlySpan<char> ReadPropertyValue(ReadOnlySpan<char> 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<string, string> properties, ReadOnlySpan<char> name, int number = 0)
private static string GetPropertyName(IReadOnlyDictionary<string, string> properties, ReadOnlySpan<char> 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<char> ReadNextPropertyWithoutName(ReadOnlySpan<char> strData, char delimiter = ',')
private static ReadOnlySpan<char> ReadNextPropertyWithoutName(ReadOnlySpan<char> 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<string, string> properties)
{
TrySetCleanSqlProperty(properties);
TrySetSqlHashProperty(properties);
TrySetFirstContextLineProperty(properties);
TrySetLastContextLineProperty(properties);
}
private bool TrySetCleanSqlProperty(Dictionary<string, string> properties)
{
if (NeedAdditionalProperty(AdditionalProperty.CleanSql) && properties.TryGetValue("Sql", out var sql))
{
properties.Add("CleanSql", ClearSql(sql).ToString());
return true;
}
return false;
}
private ReadOnlySpan<char> ClearSql(ReadOnlySpan<char> 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<string, string> 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<char> 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<string, string> properties)
{
if (NeedAdditionalProperty(AdditionalProperty.FirstContextLine) && properties.TryGetValue("Context", out var context))
{
properties.Add("FirstContextLine", GetFirstContextLine(context).ToString());
return true;
}
return false;
}
private ReadOnlySpan<char> GetFirstContextLine(ReadOnlySpan<char> context)
{
var index = context.IndexOf('\n');
if (index > 0)
return context[0..index].Trim();
else
return context;
}
private bool TrySetLastContextLineProperty(Dictionary<string, string> properties)
{
if (NeedAdditionalProperty(AdditionalProperty.LastContextLine) && properties.TryGetValue("Context", out var context))
{
properties.Add("LastContextLine", GetLastContextLine(context).ToString());
return true;
}
return false;
}
private ReadOnlySpan<char> GetLastContextLine(ReadOnlySpan<char> 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()

View File

@ -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
/// </summary>
public DateTime DateTime { get; internal set; }
public long StartTicks { get; internal set; }
public long EndTicks { get; internal set; }
/// <summary>
/// The duration of the event (in microseconds)
/// </summary>
@ -48,518 +54,552 @@ namespace OneSTools.TechLog
/// <summary>
/// A number of the thread
/// </summary>
public string OSThread => AllProperties.GetValueOrDefault("OSThread", "");
public string OSThread => AllProperties.GetValueOrDefault("OSThread", null);
/// <summary>
/// A name of the process
/// </summary>
public string Process => AllProperties.GetValueOrDefault("process", "");
public string Process => AllProperties.GetValueOrDefault("process", null);
/// <summary>
/// Время зависания процесса
/// </summary>
public string AbandonedTimestamp => AllProperties.GetValueOrDefault("abandonedTimestamp", "");
public string AbandonedTimestamp => AllProperties.GetValueOrDefault("abandonedTimestamp", null);
/// <summary>
/// Текстовое описание выполняемой операции во время загрузки конфигурации из файлов
/// </summary>
public string Action => AllProperties.GetValueOrDefault("Action", "");
public string Action => AllProperties.GetValueOrDefault("Action", null);
/// <summary>
/// Имя администратора кластера или центрального сервера
/// </summary>
public string Admin => AllProperties.GetValueOrDefault("Admin", "");
public string Admin => AllProperties.GetValueOrDefault("Admin", null);
/// <summary>
/// Имя администратора
/// </summary>
public string Administrator => AllProperties.GetValueOrDefault("Administrator", "");
public string Administrator => AllProperties.GetValueOrDefault("Administrator", null);
/// <summary>
/// Адрес текущего процесса агента сервера системы «1С:Предприятие»
/// </summary>
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);
/// <summary>
/// Уточнение требования назначения функциональности
/// </summary>
public string ApplicationExt => AllProperties.GetValueOrDefault("ApplicationExt", "");
public string ApplicationExt => AllProperties.GetValueOrDefault("ApplicationExt", null);
/// <summary>
/// Количество попыток установки соединения с процессом, завершившихся ошибкой
/// </summary>
public long Attempts => long.Parse(AllProperties.GetValueOrDefault("Attempts", "-1"));
public long? Attempts => GetLongOrNull("Attempts");
/// <summary>
/// Среднее количество исключений за последние 5 минут по другим процессам
/// </summary>
public long AvgExceptions => long.Parse(AllProperties.GetValueOrDefault("AvgExceptions", "-1"));
public long? AvgExceptions => GetLongOrNull("AvgExceptions");
/// <summary>
/// Значение показателя Доступная память в момент вывода в технологический журнал
/// </summary>
public long AvMem => long.Parse(AllProperties.GetValueOrDefault("AvMem", "-1"));
public long? AvMem => GetLongOrNull("AvMem");
/// <summary>
/// Формирование индекса полнотекстового поиска выполнялось в фоновом процессе
/// </summary>
public string BackgroundJobCreated => AllProperties.GetValueOrDefault("BackgroundJobCreated", "");
public string BackgroundJobCreated => AllProperties.GetValueOrDefault("BackgroundJobCreated", null);
/// <summary>
/// Размер в байтах тела запроса/ответа
/// </summary>
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");
/// <summary>
/// Количество обращений клиентского приложения к серверному приложению через TCP
/// </summary>
public int Calls => int.Parse(AllProperties.GetValueOrDefault("Calls", "-1"));
public int? Calls => GetIntOrNull("Calls");
/// <summary>
/// Описание проверяемого сертификата
/// </summary>
public string Certificate => AllProperties.GetValueOrDefault("certificate", "");
public string Certificate => AllProperties.GetValueOrDefault("certificate", null);
/// <summary>
/// Имя класса, в котором сгенерировано событие
/// </summary>
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");
/// <summary>
/// Очищенный текст SQL запроса
/// </summary>
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");
/// <summary>
/// Номер основного порта кластера серверов
/// </summary>
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);
/// <summary>
/// Имя компоненты платформы, в которой сгенерировано событие
/// </summary>
public string Component => AllProperties.GetValueOrDefault("Component", "");
public string Component => AllProperties.GetValueOrDefault("Component", null);
/// <summary>
/// Номер соединения с информационной базой
/// </summary>
public long Connection => long.Parse(AllProperties.GetValueOrDefault("Connection", "-1"));
public long? Connection => GetLongOrNull("Connection");
/// <summary>
/// Количество соединений, которым не хватило рабочих процессов
/// </summary>
public long Connections => long.Parse(AllProperties.GetValueOrDefault("Connections", "-1"));
public long? Connections => GetLongOrNull("Connections");
/// <summary>
/// Установленное максимальное количество соединений на один рабочий процесс
/// </summary>
public long ConnLimit => long.Parse(AllProperties.GetValueOrDefault("ConnLimit", "-1"));
public long? ConnLimit => GetLongOrNull("ConnLimit");
/// <summary>
/// Контекст исполнения
/// </summary>
public string Context => AllProperties.GetValueOrDefault("Context", "");
public string Context
{
get => AllProperties.GetValueOrDefault("Context", null);
set => AllProperties["Context"] = value;
}
/// <summary>
/// Общий размер скопированных значений при сборке мусора
/// </summary>
public long CopyBytes => long.Parse(AllProperties.GetValueOrDefault("CopyBytes", "-1"));
public long? CopyBytes => GetLongOrNull("CopyBytes");
/// <summary>
/// Длительность вызова в микросекундах
/// </summary>
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);
/// <summary>
/// Количество исключений в процессе за последние 5 минут
/// </summary>
public long CurExceptions => long.Parse(AllProperties.GetValueOrDefault("CurExceptions", "-1"));
public long? CurExceptions => GetLongOrNull("CurExceptions");
/// <summary>
/// Путь к используемой базе данных
/// </summary>
public string Database => AllProperties.GetValueOrDefault("DataBase", "");
public string Database => AllProperties.GetValueOrDefault("DataBase", null);
/// <summary>
/// Идентификатор соединения с СУБД внешнего источника данных
/// </summary>
public string DBConnID => AllProperties.GetValueOrDefault("DBConnID", "");
public string DBConnID => AllProperties.GetValueOrDefault("DBConnID", null);
/// <summary>
/// Строка соединения с внешним источником данных
/// </summary>
public string DBConnStr => AllProperties.GetValueOrDefault("DBConnStr", "");
public string DBConnStr => AllProperties.GetValueOrDefault("DBConnStr", null);
/// <summary>
/// Имя используемой копии базы данных
/// </summary>
public string DBCopy => AllProperties.GetValueOrDefault("DBCopy", "");
public string DBCopy => AllProperties.GetValueOrDefault("DBCopy", null);
/// <summary>
/// Имя СУБД, используемой для выполнения операции, которая повлекла формирование данного события технологического журнала
/// </summary>
public string DBMS => AllProperties.GetValueOrDefault("DBMS", "");
public string DBMS => AllProperties.GetValueOrDefault("DBMS", null);
/// <summary>
/// Строковое представление идентификатора соединения сервера системы «1С:Предприятие» с сервером баз данных в терминах сервера баз данных
/// </summary>
public string Dbpid => AllProperties.GetValueOrDefault("dbpid", "");
public string Dbpid => AllProperties.GetValueOrDefault("dbpid", null);
/// <summary>
/// Имя пользователя СУБД внешнего источника данных
/// </summary>
public string DBUsr => AllProperties.GetValueOrDefault("DBUsr", "");
public string DBUsr => AllProperties.GetValueOrDefault("DBUsr", null);
/// <summary>
/// Список пар транзакций, образующих взаимную блокировку
/// </summary>
public string DeadlockConnectionIntersections => AllProperties.GetValueOrDefault("DeadlockConnectionIntersections", "");
public string DeadlockConnectionIntersections => AllProperties.GetValueOrDefault("DeadlockConnectionIntersections", null);
public DeadlockConnectionIntersectionsInfo DeadlockConnectionIntersectionsInfo
=> string.IsNullOrEmpty(DeadlockConnectionIntersections) ? null : new DeadlockConnectionIntersectionsInfo(DeadlockConnectionIntersections);
/// <summary>
/// Пояснения к программному исключению
/// </summary>
public string Descr => AllProperties.GetValueOrDefault("Descr", "");
public string Descr => AllProperties.GetValueOrDefault("Descr", null);
/// <summary>
/// Текст, поясняющий выполняемое действие
/// </summary>
public string Description => AllProperties.GetValueOrDefault("description", "");
public string Description => AllProperties.GetValueOrDefault("description", null);
/// <summary>
/// Назначенный адрес рабочего процесса
/// </summary>
public string DstAddr => AllProperties.GetValueOrDefault("DstAddr", "");
public string DstAddr => AllProperties.GetValueOrDefault("DstAddr", null);
/// <summary>
/// Уникальный идентификатор назначенного рабочего процесса
/// </summary>
public string DstId => AllProperties.GetValueOrDefault("DstId", "");
public string DstId => AllProperties.GetValueOrDefault("DstId", null);
/// <summary>
/// Системный идентификатор назначенного рабочего процесса
/// </summary>
public string DstPid => AllProperties.GetValueOrDefault("DstPid", "");
public string DstPid => AllProperties.GetValueOrDefault("DstPid", null);
/// <summary>
/// Назначенное имя рабочего сервера
/// </summary>
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);
/// <summary>
/// Первая строка контекста исполнения
/// </summary>
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);
/// <summary>
/// Имя передаваемого интерфейса, метод которого вызывается удаленно
/// </summary>
public string IName => AllProperties.GetValueOrDefault("IName", "");
public string IName => AllProperties.GetValueOrDefault("IName", null);
/// <summary>
/// Количество данных, прочитанных с диска за время вызова (в байтах)
/// </summary>
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);
/// <summary>
/// Последняя строка контекста исполнения
/// </summary>
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;
}
}
}
/// <summary>
/// Поток является источником блокировки
/// </summary>
public string Lka => AllProperties.GetValueOrDefault("lka", "");
public string Lka => AllProperties.GetValueOrDefault("lka", null);
/// <summary>
/// Поток является жертвой блокировки
/// </summary>
public string Lkp => AllProperties.GetValueOrDefault("lkp", "");
public string Lkp => AllProperties.GetValueOrDefault("lkp", null);
/// <summary>
/// Номер запроса к СУБД, «кто кого заблокировал» (только для потока-жертвы блокировки). Например, ‘423’
/// </summary>
public string Lkpid => AllProperties.GetValueOrDefault("lkpid", "");
public string Lkpid => AllProperties.GetValueOrDefault("lkpid", null);
/// <summary>
/// Cписок номеров запросов к СУБД, «кто кого заблокировал» (только для потока-источника блокировки). Например, ‘271,273,274’
/// </summary>
public string Lkaid => AllProperties.GetValueOrDefault("lkaid", "");
public string Lkaid => AllProperties.GetValueOrDefault("lkaid", null);
/// <summary>
/// Массив значений из свойства lkaid
/// </summary>
public int[] LkaidArray => Lkaid != "" ? Lkaid.Split(',').Select(c => int.Parse(c)).ToArray() : default;
public int[] LkaidArray => Lkaid != "" ? Lkaid.Split(',').Select(int.Parse).ToArray() : default;
/// <summary>
/// Номер соединения источника блокировки, если поток является жертвой, например, ‘23’
/// </summary>
public string Lksrc => AllProperties.GetValueOrDefault("lksrc", "");
public string Lksrc => AllProperties.GetValueOrDefault("lksrc", null);
/// <summary>
/// Время в секундах, прошедшее с момента обнаружения, что поток является жертвой. Например: ‘15’
/// </summary>
public string Lkpto => AllProperties.GetValueOrDefault("lkpto", "");
public string Lkpto => AllProperties.GetValueOrDefault("lkpto", null);
/// <summary>
/// Время в секундах, прошедшее с момента обнаружения, что поток является источником блокировок. Например, ‘21’
/// </summary>
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");
/// <summary>
/// Имя удаленно вызываемого метода
/// </summary>
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");
/// <summary>
/// Количество данных, записанных на диск за время вызова (в байтах)
/// </summary>
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");
/// <summary>
/// План запроса, содержащегося в свойстве Sql
/// </summary>
public string PlanSQLText => AllProperties.GetValueOrDefault("planSQLText", "");
public string PlanSQLText => AllProperties.GetValueOrDefault("planSQLText", null);
/// <summary>
/// Номер основного сетевого порта процесса
/// </summary>
public int Port => int.Parse(AllProperties.GetValueOrDefault("Port", "-1"));
public int? Port => GetIntOrNull("Port");
/// <summary>
/// Имя серверного контекста, который обычно совпадает с именем информационной базы
/// </summary>
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);
/// <summary>
/// Наименование процесса
/// </summary>
public string ProcessName => AllProperties.GetValueOrDefault("ProcessName", "");
public string ProcessName => AllProperties.GetValueOrDefault("ProcessName", null);
/// <summary>
/// Адрес процесса сервера системы «1С:Предприятие», к которому относится событие
/// </summary>
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");
/// <summary>
/// Текст запроса на встроенном языке, при выполнении которого обнаружилось значение NULL в поле, для которого такое значение недопустимо
/// </summary>
public string Query => AllProperties.GetValueOrDefault("Query", "");
public string Query => AllProperties.GetValueOrDefault("Query", null);
/// <summary>
/// Перечень полей запроса, в которых обнаружены значения NULL
/// </summary>
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);
/// <summary>
/// Причина недоступности рабочего процесса
/// </summary>
public string Reason => AllProperties.GetValueOrDefault("Reason", "");
public string Reason => AllProperties.GetValueOrDefault("Reason", null);
/// <summary>
/// Имена пространств управляемых транзакционных блокировок
/// </summary>
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);
/// <summary>
/// Количество полученных записей базы данных
/// </summary>
public int Rows => int.Parse(AllProperties.GetValueOrDefault("Rows", "-1"));
public int? Rows => GetIntOrNull("Rows");
/// <summary>
/// Количество измененных записей базы данных
/// </summary>
public int RowsAffected => int.Parse(AllProperties.GetValueOrDefault("RowsAffected", "-1"));
public int? RowsAffected => GetIntOrNull("RowsAffected");
/// <summary>
/// Режим запуска процесса (приложение или сервис)
/// </summary>
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");
/// <summary>
/// Текст запроса на встроенном языке модели базы данных
/// </summary>
public string Sdbl => AllProperties.GetValueOrDefault("Sdbl", "");
public string Sdbl => AllProperties.GetValueOrDefault("Sdbl", null);
/// <summary>
/// Имя рабочего сервера
/// </summary>
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);
/// <summary>
/// Номер сеанса, назначенный текущему потоку. Если текущему потоку не назначен никакой сеанс, то свойство не добавляется
/// </summary>
public string SessionID => AllProperties.GetValueOrDefault("SessionID", "");
public string SessionID => AllProperties.GetValueOrDefault("SessionID", null);
/// <summary>
/// Текст оператора SQL
/// </summary>
public string Sql => AllProperties.GetValueOrDefault("Sql", "");
public string Sql => AllProperties.GetValueOrDefault("Sql", null);
/// <summary>
/// MD5 хеш значения свойства CleanSql
/// </summary>
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);
/// <summary>
/// Код состояния HTTP
/// </summary>
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");
/// <summary>
/// Объем занятой процессом динамической памяти на момент вывода события MEM (в байтах)
/// </summary>
public long Sz => long.Parse(AllProperties.GetValueOrDefault("Sz", "-1"));
public long? Sz => GetLongOrNull("Sz");
/// <summary>
/// Изменение объема динамической памяти, занятой процессом, с момента вывода предыдущего события MEM (в байтах)
/// </summary>
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);
/// <summary>
/// Идентификатор клиентской программы
/// </summary>
public string TApplicationName => AllProperties.GetValueOrDefault("t:applicationName", "");
public string TApplicationName => AllProperties.GetValueOrDefault("t:applicationName", null);
/// <summary>
/// Идентификатор соединения с клиентом по TCP
/// </summary>
public int TClientID => int.Parse(AllProperties.GetValueOrDefault("t:clientID", "-1"));
public int? TClientID => GetIntOrNull("t:clientID");
/// <summary>
/// Имя клиентского компьютера
/// </summary>
public string TComputerName => AllProperties.GetValueOrDefault("t:computerName", "");
public string TComputerName => AllProperties.GetValueOrDefault("t:computerName", null);
/// <summary>
/// Идентификатор соединения с информационной базой
/// </summary>
public int TConnectID => int.Parse(AllProperties.GetValueOrDefault("t:connectID", "-1"));
public int? TConnectID => GetIntOrNull("t:connectID");
/// <summary>
/// Время вывода записи в технологический журнал
/// **Для события ATTN содержит имя серверного процесса: rmngr или rphost
/// </summary>
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");
/// <summary>
/// Идентификатор активности транзакции на момент начала события:
/// 0 ‑ транзакция не была открыта;
/// 1 ‑ транзакция была открыта.
/// </summary>
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");
/// <summary>
/// Текст информационного сообщения
@ -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);
}
}
/// <summary>
/// Ресурс, к которому производится обращение
/// </summary>
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);
/// <summary>
/// Размер используемого места в хранилище, в байтах
/// </summary>
public long UsedSize => long.Parse(AllProperties.GetValueOrDefault("UsedSize", "-1"));
public long? UsedSize => GetLongOrNull("UsedSize");
/// <summary>
/// Имя пользователя информационной базы (если в информационной базе не определены пользователи, это свойство будет иметь значение DefUser).
/// Значение свойства берется из назначенного сеанса.
/// </summary>
public string Usr => AllProperties.GetValueOrDefault("Usr", "");
public string Usr => AllProperties.GetValueOrDefault("Usr", null);
/// <summary>
/// Список соединений, с которыми идет столкновение по управляемым транзакционным блокировкам
/// </summary>
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;
}
}
}