Skip to content

Instantly share code, notes, and snippets.

@teyc
Last active June 25, 2021 07:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teyc/d331084afec76038602a1aad1713f1d2 to your computer and use it in GitHub Desktop.
Save teyc/d331084afec76038602a1aad1713f1d2 to your computer and use it in GitHub Desktop.
Ships log files to Seq
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="5.0.1" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
{
"Watch": {
"EML.Services.K2.ProductGroups": "C:\\logs\\Application2\\*.txt"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using Serilog.Parsing;
using static System.Threading.Tasks.Task;
using ILogger = Serilog.ILogger;
namespace Tools.LogShipper
{
public class FileTail : BackgroundService
{
private readonly string _applicationName;
private readonly string _pathPattern;
private readonly ILogger<FileTail> _appLog;
private readonly string _directory;
private readonly string _path;
private readonly ILogger _logger;
public FileTail(string applicationName, string pathPattern, ILogger<FileTail> appLog)
{
if (applicationName == null) throw new ArgumentNullException(nameof(applicationName));
if (pathPattern == null) throw new ArgumentNullException(nameof(pathPattern));
var directory = Path.GetDirectoryName(pathPattern);
var path = pathPattern.Substring(directory.Length + 1);
_applicationName = applicationName;
_pathPattern = pathPattern;
_appLog = appLog;
_directory = directory;
_path = path;
_logger = Log.ForContext("Application", applicationName);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!Directory.Exists(_directory))
{
_logger.Warning("{Directory} not found. No logs.", _directory);
return;
}
var fileToTail = await GetFileToTail(stoppingToken);
_appLog.LogInformation("Tailing {File}", fileToTail);
await foreach (var line in ReadLines(fileToTail, stoppingToken))
{
_logger.Write(ConvertToLogEvent(line));
}
}
private LogEvent ConvertToLogEvent(string line)
{
// parses log4net files
// line:
// 2021-06-25 16:07:52,353 [61] INFO MyModuleName - Attempting to dispatch Request: GetProductGroups.
var (level, message) =
line.Contains(" INFO ") ? (LogEventLevel.Information, line.Split(" INFO ", 2)[1]) :
line.Contains(" DEBUG ") ? (LogEventLevel.Debug, line.Split(" DEBUG ", 2)[1]) :
line.Contains(" WARN ") ? (LogEventLevel.Warning, line.Split(" WARN ", 2)[1]) :
line.Contains(" ERROR ") ? (LogEventLevel.Error, line.Split(" ERROR ", 2)[1]) :
(LogEventLevel.Verbose, line);
return new LogEvent(DateTimeOffset.Now, level, null,
new MessageTemplate(message, Array.Empty<MessageTemplateToken>()), Array.Empty<LogEventProperty>());
}
private async IAsyncEnumerable<string> ReadLines(string fileToTail, CancellationToken stoppingToken)
{
using var stream = File.Open(fileToTail, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream);
reader.BaseStream.Seek(0, SeekOrigin.End);
while (!stoppingToken.IsCancellationRequested)
{
var line = await reader.ReadLineAsync();
if (reader.BaseStream.Length < reader.BaseStream.Position)
reader.BaseStream.Seek(0, SeekOrigin.Begin);
if (line != null)
{
yield return line;
}
else await Delay(500, stoppingToken);
}
}
private async Task<string?> GetFileToTail(CancellationToken stoppingToken)
{
again:
var files = Directory.GetFiles(_directory, _path, SearchOption.TopDirectoryOnly);
var fileToShip = files.OrderByDescending(file => new FileInfo(file).LastWriteTime).FirstOrDefault();
if (fileToShip == null)
{
await Delay(TimeSpan.FromSeconds(10), stoppingToken);
goto again;
}
return fileToShip;
}
}
}
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
namespace Tools.LogShipper
{
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration().WriteTo.Seq("http://localhost:5341/").CreateLogger();
var hostStatic = CreateHostBuilder(args).Build();
hostStatic.Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureAppConfiguration((hostContext, configurationBuilder) =>
{
configurationBuilder.AddJsonFile("appsettings.json");
})
.ConfigureServices((hostContext, services) =>
{
foreach (var section in hostContext.Configuration.GetSection("Watch").GetChildren())
{
services.AddSingleton<IHostedService>(_ => new FileTail(section.Key, section.Value, _.GetService<ILogger<FileTail>>()));
}
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment