mirror of
https://github.com/akpaevj/OneSTools.TechLog.git
synced 2024-12-06 08:16:09 +02:00
Полная перерабокта библиотеки, улучшение производительности, добавление проектов для экспорта ТЖ в сторонние БД
This commit is contained in:
parent
108fc8ea4b
commit
d8cc243537
11
OneSTools.TechLog.Exporter.Core/ITechLogFolderReader.cs
Normal file
11
OneSTools.TechLog.Exporter.Core/ITechLogFolderReader.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.Core
|
||||
{
|
||||
public interface ITechLogFolderReader : IDisposable
|
||||
{
|
||||
Task StartAsync(string logFolder, int portion, bool liveMode = false, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
9
OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs
Normal file
9
OneSTools.TechLog.Exporter.Core/ITechLogStorage.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.Core
|
||||
{
|
||||
public interface ITechLogStorage
|
||||
{
|
||||
Task WriteItemsAsync(TechLogItem[] items);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OneSTools.TechLog\OneSTools.TechLog.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
41
OneSTools.TechLog.Exporter.Core/TechLogExporterService.cs
Normal file
41
OneSTools.TechLog.Exporter.Core/TechLogExporterService.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.Core
|
||||
{
|
||||
public class TechLogExporterService : BackgroundService
|
||||
{
|
||||
private IConfiguration _configuration;
|
||||
private readonly ILogger<TechLogExporterService> _logger;
|
||||
private readonly ITechLogFolderReader _techLogFolderReader;
|
||||
private string _logFolder;
|
||||
private int _portion;
|
||||
|
||||
public TechLogExporterService(IConfiguration configuration, ILogger<TechLogExporterService> logger, ITechLogFolderReader techLogFolderReader)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_techLogFolderReader = techLogFolderReader;
|
||||
|
||||
_logFolder = configuration.GetValue("Exporter:LogFolder", "");
|
||||
|
||||
if (_logFolder == "")
|
||||
throw new Exception("Log folder's path is not set");
|
||||
|
||||
_portion = configuration.GetValue("Exporter", 10000);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await _techLogFolderReader.StartAsync(_logFolder, _portion, true, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
121
OneSTools.TechLog.Exporter.Core/TechLogFolderReader.cs
Normal file
121
OneSTools.TechLog.Exporter.Core/TechLogFolderReader.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OneSTools.TechLog;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.Core
|
||||
{
|
||||
public class TechLogFolderReader : IDisposable, ITechLogFolderReader
|
||||
{
|
||||
private readonly ILogger<TechLogFolderReader> _logger;
|
||||
private ITechLogStorage _techLogStorage;
|
||||
private string _folder;
|
||||
private bool _liveMode;
|
||||
private ActionBlock<TechLogItem[]> _writeBlock;
|
||||
private BatchBlock<TechLogItem> _batchBlock;
|
||||
private TransformBlock<string, TechLogItem> _parseBlock;
|
||||
private ActionBlock<string> _readBlock;
|
||||
|
||||
public TechLogFolderReader(ILogger<TechLogFolderReader> logger, ITechLogStorage techLogStorage)
|
||||
{
|
||||
_logger = logger;
|
||||
_techLogStorage = techLogStorage;
|
||||
}
|
||||
|
||||
public async Task StartAsync(string folder, int portion, bool liveMode = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_folder = folder;
|
||||
_liveMode = liveMode;
|
||||
|
||||
var maxDegree = Environment.ProcessorCount * 10;
|
||||
|
||||
_writeBlock = new ActionBlock<TechLogItem[]>(_techLogStorage.WriteItemsAsync, new ExecutionDataflowBlockOptions()
|
||||
{
|
||||
BoundedCapacity = 3,
|
||||
CancellationToken = cancellationToken,
|
||||
MaxDegreeOfParallelism = maxDegree,
|
||||
});
|
||||
_batchBlock = new BatchBlock<TechLogItem>(portion, new GroupingDataflowBlockOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
BoundedCapacity = portion
|
||||
});
|
||||
_parseBlock = new TransformBlock<string, TechLogItem>(ParseItemData, new ExecutionDataflowBlockOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = maxDegree,
|
||||
BoundedCapacity = portion / 2,
|
||||
CancellationToken = cancellationToken
|
||||
});
|
||||
_readBlock = new ActionBlock<string>(str => ReadItemsData(str, _parseBlock), new ExecutionDataflowBlockOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = maxDegree,
|
||||
CancellationToken = cancellationToken
|
||||
}); ;
|
||||
|
||||
_parseBlock.LinkTo(_batchBlock);
|
||||
_batchBlock.LinkTo(_writeBlock);
|
||||
|
||||
var logFiles = GetLogFiles();
|
||||
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
await _readBlock.SendAsync(logFile);
|
||||
}
|
||||
|
||||
await _writeBlock.Completion;
|
||||
}
|
||||
|
||||
private string[] GetLogFiles()
|
||||
{
|
||||
return Directory.GetFiles(_folder, "*.log", SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
private void ReadItemsData(string logPath, ITargetBlock<string> nextblock)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(logPath);
|
||||
|
||||
var fileDateTime = "20" +
|
||||
fileName.Substring(0, 2) +
|
||||
"-" +
|
||||
fileName.Substring(2, 2) +
|
||||
"-" +
|
||||
fileName.Substring(4, 2) +
|
||||
" " +
|
||||
fileName.Substring(6, 2);
|
||||
|
||||
using var reader = new TechLogReader(logPath);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var itemData = reader.ReadItemData();
|
||||
|
||||
if (itemData != null)
|
||||
PostData(nextblock, fileDateTime + ":" + itemData);
|
||||
}
|
||||
}
|
||||
|
||||
private void PostData<T>(ITargetBlock<T> nextblock, T data)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (nextblock.Post(data))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private TechLogItem ParseItemData(string itemData)
|
||||
{
|
||||
return TechLogReader.ParseItemData(itemData);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UserSecretsId>dotnet-OneSTools.TechLog.Exporter.ElasticSearch-146D8E25-B976-4FAB-A231-A91F8DD2EEF5</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NEST" Version="7.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OneSTools.TechLog.Exporter.Core\OneSTools.TechLog.Exporter.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
41
OneSTools.TechLog.Exporter.ElasticSearch/Program.cs
Normal file
41
OneSTools.TechLog.Exporter.ElasticSearch/Program.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OneSTools.TechLog.Exporter.Core;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.ElasticSearch
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
var configuration = hostContext.Configuration;
|
||||
var host = configuration.GetValue("ElasticSearch:Host", "");
|
||||
var port = configuration.GetValue("ElasticSearch:Port", 9200);
|
||||
var index = configuration.GetValue("ElasticSearch:Index", "");
|
||||
var separation = configuration.GetValue("ElasticSearch:Separation", "");
|
||||
|
||||
services.AddSingleton<ITechLogStorage>(sp =>
|
||||
{
|
||||
var logger = sp.GetService<ILogger<TechLogStorage>>();
|
||||
|
||||
return new TechLogStorage(logger, host, port, index, separation);
|
||||
});
|
||||
services.AddSingleton<ITechLogFolderReader, TechLogFolderReader>();
|
||||
services.AddHostedService<TechLogExporterService>();
|
||||
});
|
||||
}
|
||||
}
|
77
OneSTools.TechLog.Exporter.ElasticSearch/TechLogStorage.cs
Normal file
77
OneSTools.TechLog.Exporter.ElasticSearch/TechLogStorage.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nest;
|
||||
using OneSTools.TechLog.Exporter.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OneSTools.TechLog.Exporter.ElasticSearch
|
||||
{
|
||||
public class TechLogStorage : ITechLogStorage
|
||||
{
|
||||
private readonly ILogger<TechLogStorage> _logger;
|
||||
private string _index;
|
||||
private string _separation;
|
||||
ElasticClient _client;
|
||||
|
||||
public TechLogStorage(ILogger<TechLogStorage> logger, string host, int port = 9200, string index = "", string separation = "")
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
var uri = new Uri($"{host}:{port}");
|
||||
_index = $"{index}-tl";
|
||||
|
||||
var settings = new ConnectionSettings(uri);
|
||||
settings.DefaultIndex(_index);
|
||||
|
||||
_separation = separation;
|
||||
|
||||
_client = new ElasticClient(settings);
|
||||
var response = _client.Ping();
|
||||
|
||||
if (!response.IsValid)
|
||||
throw response.OriginalException;
|
||||
}
|
||||
|
||||
public async Task WriteItemsAsync(TechLogItem[] items)
|
||||
{
|
||||
var data = new List<(string IndexName, TechLogItem[] Items)>();
|
||||
|
||||
switch (_separation)
|
||||
{
|
||||
case "H":
|
||||
var groups = items.GroupBy(c => c.DateTime.ToString("yyyyMMddhh")).OrderBy(c => c.Key);
|
||||
foreach (IGrouping<string, TechLogItem> item in groups)
|
||||
data.Add(($"{_index}-{item.Key}", item.ToArray()));
|
||||
break;
|
||||
case "D":
|
||||
groups = items.GroupBy(c => c.DateTime.ToString("yyyyMMdd")).OrderBy(c => c.Key);
|
||||
foreach (IGrouping<string, TechLogItem> item in groups)
|
||||
data.Add(($"{_index}-{item.Key}", item.ToArray()));
|
||||
break;
|
||||
case "M":
|
||||
groups = items.GroupBy(c => c.DateTime.ToString("yyyyMM")).OrderBy(c => c.Key);
|
||||
foreach (IGrouping<string, TechLogItem> item in groups)
|
||||
data.Add(($"{_index}-{item.Key}", item.ToArray()));
|
||||
break;
|
||||
default:
|
||||
data.Add(($"{_index}-all", items));
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ((string IndexName, TechLogItem[] Entities) item in data)
|
||||
{
|
||||
var responseItems = await _client.IndexManyAsync(item.Entities, item.IndexName);
|
||||
|
||||
if (!responseItems.IsValid)
|
||||
{
|
||||
throw responseItems.OriginalException;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"{DateTime.Now:hh:mm:ss:fffff} has written {item.Entities.Length}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
18
OneSTools.TechLog.Exporter.ElasticSearch/appsettings.json
Normal file
18
OneSTools.TechLog.Exporter.ElasticSearch/appsettings.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Exporter": {
|
||||
"LogFolder": "C:\\Users\\akpaev.e.ENTERPRISE\\Desktop\\TechLog"
|
||||
},
|
||||
"ElasticSearch": {
|
||||
"Host": "http://192.168.0.95",
|
||||
"Port": 9200,
|
||||
"Index": "upp-main",
|
||||
"Separation": "H" // H - hour, D - day, M - Month
|
||||
}
|
||||
}
|
@ -5,7 +5,9 @@ VisualStudioVersion = 16.0.29318.209
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.TechLog", "OneSTools.TechLog\OneSTools.TechLog.csproj", "{BC6E4DCE-2722-4E6F-BCBC-8945DE1127DD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneSTools.TechLogConsoleApp", "OneSTools.TechLogConsoleApp\OneSTools.TechLogConsoleApp.csproj", "{8C9CA150-5813-4B2B-99B4-B471708DFE8C}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneSTools.TechLog.Exporter.ElasticSearch", "OneSTools.TechLog.Exporter.ElasticSearch\OneSTools.TechLog.Exporter.ElasticSearch.csproj", "{6C61BC30-BA4E-428D-A805-1F3B7D9B4D00}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneSTools.TechLog.Exporter.Core", "OneSTools.TechLog.Exporter.Core\OneSTools.TechLog.Exporter.Core.csproj", "{02C37C84-911F-4725-A1EC-B81FF8F16227}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -17,10 +19,14 @@ Global
|
||||
{BC6E4DCE-2722-4E6F-BCBC-8945DE1127DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BC6E4DCE-2722-4E6F-BCBC-8945DE1127DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BC6E4DCE-2722-4E6F-BCBC-8945DE1127DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C9CA150-5813-4B2B-99B4-B471708DFE8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C9CA150-5813-4B2B-99B4-B471708DFE8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C9CA150-5813-4B2B-99B4-B471708DFE8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C9CA150-5813-4B2B-99B4-B471708DFE8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6C61BC30-BA4E-428D-A805-1F3B7D9B4D00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C61BC30-BA4E-428D-A805-1F3B7D9B4D00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C61BC30-BA4E-428D-A805-1F3B7D9B4D00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C61BC30-BA4E-428D-A805-1F3B7D9B4D00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{02C37C84-911F-4725-A1EC-B81FF8F16227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{02C37C84-911F-4725-A1EC-B81FF8F16227}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{02C37C84-911F-4725-A1EC-B81FF8F16227}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{02C37C84-911F-4725-A1EC-B81FF8F16227}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -1,15 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Akpaev Evgeniy</Authors>
|
||||
<Company>Akpaev Evgeniy</Company>
|
||||
<Authors>Akpaev Evgeny</Authors>
|
||||
<Company>Akpaev Evgeny</Company>
|
||||
<RepositoryUrl>https://github.com/akpaevj/OneSTools.TechLog</RepositoryUrl>
|
||||
<PackageProjectUrl></PackageProjectUrl>
|
||||
<Copyright>Akpaev Evgeniy</Copyright>
|
||||
<PackageProjectUrl>https://github.com/akpaevj/OneSTools.TechLog</PackageProjectUrl>
|
||||
<Copyright>Akpaev Evgeny</Copyright>
|
||||
<Description>Библиотека для парсинга технологического журнала 1С</Description>
|
||||
<Version>1.0.4</Version>
|
||||
<Version>2.0.0</Version>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<PackageIcon>onestools_icon_nuget.png</PackageIcon>
|
||||
<PackageLicenseExpression></PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@ -19,7 +22,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" />
|
||||
<None Include="C:\Users\akpaev.e.ENTERPRISE\source\repos\onestools_icon_nuget.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
15
OneSTools.TechLog/TechLogItem.cs
Normal file
15
OneSTools.TechLog/TechLogItem.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OneSTools.TechLog
|
||||
{
|
||||
public class TechLogItem
|
||||
{
|
||||
public DateTime DateTime { get; set; }
|
||||
public long Duration { get; set; }
|
||||
public string Event { get; set; }
|
||||
public int Level { get; set; }
|
||||
|
||||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OneSTools.TechLog
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents methods for the parsing of the 1C technological log
|
||||
/// </summary>
|
||||
public class TechLogParser
|
||||
{
|
||||
private string folder;
|
||||
private Action<Dictionary<string, string>> eventHandler;
|
||||
private ExecutionDataflowBlockOptions readBlockOptions;
|
||||
private ExecutionDataflowBlockOptions parseBlockOptions;
|
||||
private ExecutionDataflowBlockOptions eventHandlerBlockOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Folder of the technological log data
|
||||
/// </summary>
|
||||
public string Folder { get => folder; private set => folder = value; }
|
||||
/// <summary>
|
||||
/// Action for the event handling
|
||||
/// </summary>
|
||||
public Action<Dictionary<string, string>> EventHandler { get => eventHandler; set => eventHandler = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of TechLogParser class
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <param name="eventHandler"></param>
|
||||
public TechLogParser(string folder, Action<Dictionary<string, string>> eventHandler)
|
||||
{
|
||||
Folder = folder;
|
||||
EventHandler = eventHandler;
|
||||
|
||||
readBlockOptions = new ExecutionDataflowBlockOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount
|
||||
};
|
||||
|
||||
parseBlockOptions = new ExecutionDataflowBlockOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
||||
BoundedCapacity = 10000
|
||||
};
|
||||
|
||||
eventHandlerBlockOptions = new ExecutionDataflowBlockOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
||||
BoundedCapacity = 10000
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts parsing of the technological log data
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task Parse()
|
||||
{
|
||||
var eventHandlerBlock = new ActionBlock<Dictionary<string, string>>(EventHandler, eventHandlerBlockOptions);
|
||||
var parseEventBlock = new TransformBlock<string, Dictionary<string, string>>(ParseEventData, parseBlockOptions);
|
||||
var readFileBlock = new ActionBlock<string>((filePath) => ReadFile(filePath, parseEventBlock), readBlockOptions);
|
||||
|
||||
parseEventBlock.LinkTo(eventHandlerBlock);
|
||||
|
||||
var files = GetTechLogFiles();
|
||||
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
SendDataToNextBlock(filePath, readFileBlock);
|
||||
}
|
||||
|
||||
var readBlockTask = readFileBlock.Completion.ContinueWith(c => parseEventBlock.Complete());
|
||||
var parseEventBlockTask = parseEventBlock.Completion.ContinueWith(c => eventHandlerBlock.Complete());
|
||||
|
||||
readFileBlock.Complete();
|
||||
|
||||
await Task.WhenAll(readBlockTask, parseEventBlockTask, eventHandlerBlock.Completion);
|
||||
}
|
||||
|
||||
private void ReadFile(string filePath, ITargetBlock<string> nextBlock)
|
||||
{
|
||||
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
string fileDateTime = GetFileDateTime(filePath);
|
||||
|
||||
StringBuilder currentEvent = new StringBuilder();
|
||||
bool firstEvent = true;
|
||||
|
||||
do
|
||||
{
|
||||
var currentLine = reader.ReadLine();
|
||||
|
||||
if (Regex.IsMatch(currentLine, @"^\d\d:\d\d\.", RegexOptions.Compiled))
|
||||
{
|
||||
if (firstEvent)
|
||||
{
|
||||
firstEvent = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SendDataToNextBlock(fileDateTime + ":" + currentEvent.ToString(), nextBlock);
|
||||
|
||||
currentEvent.Clear();
|
||||
}
|
||||
|
||||
currentEvent.AppendLine(currentLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentEvent.AppendLine(currentLine);
|
||||
}
|
||||
}
|
||||
while (!reader.EndOfStream);
|
||||
|
||||
SendDataToNextBlock(fileDateTime + ":" + currentEvent.ToString(), nextBlock);
|
||||
}
|
||||
}
|
||||
private Dictionary<string, string> ParseEventData(string eventData)
|
||||
{
|
||||
var properties = new Dictionary<string, string>
|
||||
{
|
||||
["EventName"] = Regex.Match(eventData, @",.*?,", RegexOptions.Compiled).ToString().Trim(','),
|
||||
["DateTime"] = Regex.Match(eventData, @"^.*?\.\d+", RegexOptions.Compiled).ToString(),
|
||||
["Duration"] = Regex.Match(eventData, @"-\d+?,", RegexOptions.Compiled).ToString().Trim('-', ',')
|
||||
};
|
||||
|
||||
var props = Regex.Matches(eventData, @",[\w:]+=.*?(?=(,[\w:]+=|$))", RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.Compiled);
|
||||
|
||||
for (int x = 0; x < props.Count; x++)
|
||||
{
|
||||
var propText = props[x].ToString();
|
||||
var splInd = propText.IndexOf('=');
|
||||
var propName = propText.Substring(0, splInd).Trim(',');
|
||||
var propVal = propText.Substring(splInd + 1).Trim('\'', '"');
|
||||
|
||||
properties[propName] = propVal;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
private void SendDataToNextBlock<T>(T data, ITargetBlock<T> nextBlock)
|
||||
{
|
||||
while (!nextBlock.Post(data)) ;
|
||||
}
|
||||
private string[] GetTechLogFiles()
|
||||
{
|
||||
return Directory.GetFiles(Folder, "*.log");
|
||||
}
|
||||
private string GetFileDateTime(string filePath)
|
||||
{
|
||||
var info = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
return "20" + info.Substring(0, 2) + "-" + info.Substring(2, 2) + "-" + info.Substring(4, 2) + " " + info.Substring(6, 2);
|
||||
}
|
||||
}
|
||||
}
|
183
OneSTools.TechLog/TechLogReader.cs
Normal file
183
OneSTools.TechLog/TechLogReader.cs
Normal file
@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OneSTools.TechLog
|
||||
{
|
||||
public class TechLogReader : IDisposable
|
||||
{
|
||||
private string _logPath;
|
||||
private string _fileName;
|
||||
private FileStream _fileStream;
|
||||
private StreamReader _streamReader;
|
||||
private StringBuilder _currentData = new StringBuilder();
|
||||
|
||||
public TechLogReader(string logPath)
|
||||
{
|
||||
_logPath = logPath;
|
||||
_fileName = Path.GetFileNameWithoutExtension(_logPath);
|
||||
}
|
||||
|
||||
public TechLogItem ReadNextItem(CancellationToken cancellationToken = default)
|
||||
{
|
||||
InitializeStream();
|
||||
|
||||
var itemData = ReadItemData(cancellationToken);
|
||||
|
||||
if (itemData == null)
|
||||
return null;
|
||||
|
||||
return ParseItemData(itemData, cancellationToken);
|
||||
}
|
||||
|
||||
public string ReadItemData(CancellationToken cancellationToken = default)
|
||||
{
|
||||
InitializeStream();
|
||||
|
||||
var currentLine = "";
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
currentLine = _streamReader.ReadLine();
|
||||
|
||||
if (currentLine == null)
|
||||
{
|
||||
if (_currentData.Length > 0)
|
||||
break;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
if (currentLine == null || _currentData.Length > 0 && Regex.IsMatch(currentLine, @"^\d\d:\d\d\.", RegexOptions.Compiled))
|
||||
break;
|
||||
else
|
||||
_currentData.AppendLine(currentLine);
|
||||
}
|
||||
|
||||
var _strData = _currentData.ToString().Trim();
|
||||
|
||||
_currentData.Clear();
|
||||
|
||||
if (currentLine != null)
|
||||
_currentData.AppendLine(currentLine);
|
||||
|
||||
return _strData;
|
||||
}
|
||||
|
||||
public static TechLogItem ParseItemData(string itemData, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = new TechLogItem();
|
||||
|
||||
int startPosition = 0;
|
||||
|
||||
var dtd = ReadNextPropertyWithoutName(itemData, ref startPosition, ',');
|
||||
var dtdLength = dtd.Length;
|
||||
var dtEndIndex = dtd.LastIndexOf('-');
|
||||
data.DateTime = DateTime.Parse(dtd.Substring(0, dtEndIndex));
|
||||
startPosition -= dtdLength - dtEndIndex;
|
||||
|
||||
data.Duration = long.Parse(ReadNextPropertyWithoutName(itemData, ref startPosition, ','));
|
||||
data.Event = ReadNextPropertyWithoutName(itemData, ref startPosition, ',');
|
||||
data.Level = int.Parse(ReadNextPropertyWithoutName(itemData, ref startPosition, ','));
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var (Name, Value) = ReadNextProperty(itemData, ref startPosition);
|
||||
|
||||
if (data.Properties.ContainsKey(Name))
|
||||
{
|
||||
data.Properties.Add(GetPropertyName(data, Name, 0), Value);
|
||||
}
|
||||
else
|
||||
data.Properties.Add(Name, Value);
|
||||
|
||||
if (startPosition >= itemData.Length)
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static string GetPropertyName(TechLogItem item, string name, int number = 0)
|
||||
{
|
||||
var currentName = $"{name}{number}";
|
||||
|
||||
if (!item.Properties.ContainsKey(currentName))
|
||||
return currentName;
|
||||
else
|
||||
{
|
||||
return GetPropertyName(item, name, number + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadNextPropertyWithoutName(string strData, ref int startPosition, char delimiter = ',')
|
||||
{
|
||||
var endPosition = strData.IndexOf(delimiter, startPosition);
|
||||
var value = strData.Substring(startPosition, endPosition - startPosition);
|
||||
startPosition = endPosition + 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static (string Name, string Value) ReadNextProperty(string strData, ref int startPosition)
|
||||
{
|
||||
var equalPosition = strData.IndexOf('=', startPosition);
|
||||
var name = strData.Substring(startPosition, equalPosition - startPosition);
|
||||
startPosition = equalPosition + 1;
|
||||
|
||||
if (startPosition == strData.Length)
|
||||
return (name, "");
|
||||
|
||||
var nextChar = strData[startPosition];
|
||||
|
||||
int endPosition;
|
||||
switch (nextChar)
|
||||
{
|
||||
case '\'':
|
||||
endPosition = strData.IndexOf("\',", startPosition);
|
||||
break;
|
||||
case ',':
|
||||
startPosition++;
|
||||
return (name, "");
|
||||
case '"':
|
||||
endPosition = strData.IndexOf("\",", startPosition);
|
||||
break;
|
||||
default:
|
||||
endPosition = strData.IndexOf(',', startPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (endPosition < 0)
|
||||
endPosition = strData.Length;
|
||||
|
||||
var value = strData.Substring(startPosition, endPosition - startPosition);
|
||||
startPosition = endPosition + 1;
|
||||
|
||||
return (name, value);
|
||||
}
|
||||
|
||||
private void InitializeStream()
|
||||
{
|
||||
if (_fileStream == null)
|
||||
{
|
||||
_fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
_streamReader = new StreamReader(_fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fileStream == null)
|
||||
{
|
||||
_streamReader.Dispose();
|
||||
|
||||
_fileStream = null;
|
||||
_streamReader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OneSTools.TechLog\OneSTools.TechLog.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OneSTools.TechLog;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OneSTools.TechLogConsoleApp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static object locker = new object();
|
||||
static int i = 0;
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var parser = new TechLogParser(@"C:\Users\akpaev.e.ENTERPRISE\Desktop\ExpertTools\tl", EventHandler);
|
||||
|
||||
var watch = new Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
await parser.Parse();
|
||||
|
||||
watch.Stop();
|
||||
|
||||
Console.WriteLine($"Считано событий: {i}");
|
||||
Console.WriteLine($"Время выполнения: {watch.Elapsed}");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
private static void EventHandler(Dictionary<string, string> eventData)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user