mirror of
https://github.com/akpaevj/OneSTools.TechLog.git
synced 2025-02-16 18:34:27 +02:00
Фиксация промежуточной работы
This commit is contained in:
parent
5957aa4556
commit
6a49a7d672
@ -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()
|
||||
|
@ -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 [остаток строки не уместился]";.
|
||||
/// </summary>
|
||||
internal static string CreateItemsTable {
|
||||
get {
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter
|
||||
namespace OneSTools.TechLog.Exporter
|
||||
{
|
||||
public enum StorageType
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Default": "Debug",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
26
OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs
Normal file
26
OneSTools.TechLog/DeadlockConnectionIntersectionsInfo.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
36
OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs
Normal file
36
OneSTools.TechLog/DeadlockConnectionIntersectionsPair.cs
Normal 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('"')));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
OneSTools.TechLog/LocksInfo.cs
Normal file
26
OneSTools.TechLog/LocksInfo.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
34
OneSTools.TechLog/LocksInfoRegion.cs
Normal file
34
OneSTools.TechLog/LocksInfoRegion.cs
Normal 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('"')));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace OneSTools.TechLog
|
||||
{
|
||||
public class LogReaderTimeoutException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user