You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/Logging/Implementation/TextFileLogger.cs

489 lines
14 KiB

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using ZeroLevel.Services.FileSystem;
namespace ZeroLevel.Logging
{
public sealed class TextFileLoggerOptions
{
public TextFileLoggerOptions()
{
Folder = null!;
LimitFileSize = 0;
TextEncoding = DEFAULT_ENCODING;
RemoveOlderThen = TimeSpan.MinValue;
RemoveOldFiles = false;
ZipOldFiles = false;
}
public static readonly Encoding DEFAULT_ENCODING = Encoding.UTF8;
internal string Folder { get; private set; }
internal long LimitFileSize { get; private set; }
internal Encoding TextEncoding { get; private set; }
/// <summary>
/// Delete files older than (aging period)
/// </summary>
internal TimeSpan RemoveOlderThen { get; private set; }
/// <summary>
/// Delete outdate files
/// </summary>
internal bool RemoveOldFiles { get; private set; }
/// <summary>
/// Archive files
/// </summary>
internal bool ZipOldFiles { get; private set; }
internal TextFileLoggerOptions Commit()
{
if (string.IsNullOrWhiteSpace(Folder))
{
throw new ArgumentException("Not set log folder path");
}
return this;
}
/// <summary>
/// Enable automatic archiving
/// </summary>
public TextFileLoggerOptions EnableAutoArchiving()
{
this.ZipOldFiles = true;
return this;
}
/// <summary>
///Enable automatic deletion of outdate files.
/// </summary>
/// <param name="age">The age of the log file at which removal is required</param>
public TextFileLoggerOptions EnableAutoCleaning(TimeSpan age)
{
this.RemoveOldFiles = true;
this.RemoveOlderThen = age;
return this;
}
public TextFileLoggerOptions SetFolderPath(string folder)
{
if (folder.IndexOf(':') < 0)
{
this.Folder = Path.Combine(Configuration.BaseDirectory, folder);
}
else
{
this.Folder = folder;
}
return this;
}
public TextFileLoggerOptions SetMaximumFileSizeInKb(long size)
{
this.LimitFileSize = size;
return this;
}
public TextFileLoggerOptions SetEncoding(Encoding encoding)
{
this.TextEncoding = encoding;
return this;
}
internal static TextFileLoggerOptions CreateOptionsBy(IConfiguration config, string path, string logPrefix)
{
var options = new TextFileLoggerOptions().
SetFolderPath(path);
config.DoWithFirst<long>($"{logPrefix}backlog", backlog =>
{
if (backlog > 0)
{
Log.Backlog(backlog);
}
});
config.DoWithFirst<bool>($"{logPrefix}archive", enable =>
{
if (enable)
{
options.EnableAutoArchiving();
}
});
config.DoWithFirst<int>($"{logPrefix}sizeinkb", size =>
{
if (size >= 1)
{
options.SetMaximumFileSizeInKb(size);
}
});
config.DoWithFirst<int>($"{logPrefix}cleanolderdays", days =>
{
if (days > 0)
{
options.EnableAutoCleaning(TimeSpan.FromDays(days));
}
});
return options;
}
}
public sealed class TextFileLogger : ILogger
{
#region Fields
private readonly TextFileLoggerOptions _options;
private int _todayCountLogFiles = 0;
/// <summary>
/// Current log file
/// </summary>
private string _currentLogFile;
/// <summary>
/// Stream to output to file
/// </summary>
private TextWriter _writer;
/// <summary>
/// Lock on re-create file
/// </summary>
private readonly object _fileRecreating = new object();
private readonly HashSet<long> _taskKeys = new HashSet<long>();
public static readonly Encoding DEFAULT_ENCODING = Encoding.UTF8;
#endregion Fields
#region Ctors
/// <summary>
/// Constructor indicating the directory for recording log files, the encoding is set to Unicode by default.
/// </summary>
public TextFileLogger(TextFileLoggerOptions options)
{
_options = options.Commit();
if (!Directory.Exists(_options.Folder))
{
var dir = Directory.CreateDirectory(_options.Folder);
if (dir.Exists == false)
{
throw new ArgumentException($"Can't create or found directory '{_options.Folder}'");
}
}
RecreateLogFile();
// Maintenance tasks
if (_options.LimitFileSize > 0)
{
_taskKeys.Add(Sheduller.RemindEvery(TimeSpan.FromSeconds(20), CheckRecreateFileLogByOversize));
}
if (_options.RemoveOldFiles)
{
_taskKeys.Add(Sheduller.RemindEvery(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(60), RemoveOldFiles));
}
_taskKeys.Add(Sheduller.RemindEveryNonlinearPeriod(() =>
(DateTime.Now.AddDays(1).Date - DateTime.Now).Add(TimeSpan.FromMilliseconds(100)),
RecreateLogFile));
}
#endregion Ctors
#region Private member
private void RemoveOldFiles()
{
try
{
var dir = new DirectoryInfo(_options.Folder);
dir.
GetFiles().
Do(files =>
{
foreach (var file in files.Where(f => (DateTime.Now - f.CreationTime) > _options.RemoveOlderThen).ToArray())
{
file.Delete();
}
});
dir = null!;
}
catch { }
}
/// <summary>
/// Closing the current log
/// </summary>
private void CloseCurrentWriter()
{
if (_writer != null!)
{
_writer.Flush();
_writer.Close();
_writer.Dispose();
_writer = null!;
}
}
private string GetNextFileName()
{
string fileName = Path.Combine(_options.Folder, string.Format(CultureInfo.CurrentCulture, "{0:yyyyMMdd}_{1:D4}.txt", DateTime.Now, _todayCountLogFiles));
if (_options.LimitFileSize > 0)
{
if (!File.Exists(fileName))
{
_todayCountLogFiles = 0;
}
else
{
while (File.Exists(fileName))
{
var length = (new FileInfo(fileName).Length >> 10);
if (length >= _options.LimitFileSize)
{
_todayCountLogFiles++;
fileName = Path.Combine(_options.Folder, string.Format(CultureInfo.CurrentCulture, "{0:yyyyMMdd}_{1:D4}.txt", DateTime.Now, _todayCountLogFiles));
}
else
{
break;
}
}
}
}
return fileName;
}
private void CheckRecreateFileLogByOversize()
{
var fi = new FileInfo(_currentLogFile);
if (fi.Exists && (fi.Length >> 10) >= _options.LimitFileSize)
{
RecreateLogFile();
}
fi = null!;
}
/// <summary>
/// Checking the name of the log file (changes when the date changes to the next day)
/// </summary>
private void RecreateLogFile()
{
lock (_fileRecreating)
{
var nextFileName = GetNextFileName();
CloseCurrentWriter();
Stream stream = null!;
PackOldLogFile(_currentLogFile);
try
{
_currentLogFile = nextFileName;
stream = new FileStream(_currentLogFile, FileMode.Append, FileAccess.Write, FileShare.Read);
_writer = new StreamWriter(stream, _options.TextEncoding);
stream = null!;
}
catch
{
if (stream != null!)
{
stream.Dispose();
}
throw;
}
}
}
private void PackOldLogFile(string filePath)
{
if (null != filePath && File.Exists(filePath) && _options.ZipOldFiles)
{
using (var stream = new FileStream($"{filePath}.zip", FileMode.Create))
{
using (var zipStream = new GZipStream(stream, CompressionLevel.Optimal, false))
{
var buffer = new byte[1024 * 1024];
int count = 0;
using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.None)))
{
while ((count = reader.Read(buffer, 0, buffer.Length)) > 0)
{
zipStream.Write(buffer, 0, count);
}
reader.Close();
}
buffer = null!;
}
stream.Close();
}
File.Delete(filePath);
}
}
#endregion Private member
#region ILog
public void Write(LogLevel level, string message)
{
if (false == _disposed)
{
lock (_fileRecreating)
{
if (level == LogLevel.Raw)
{
_writer.WriteLine(message);
}
else
{
var meta = string.Format("[{0:dd'.'MM'.'yyyy HH':'mm':'ss} {1}]\t", DateTime.Now, LogLevelNameMapping.CompactName(level));
_writer.Write(meta);
_writer.WriteLine(message);
}
_writer.Flush();
}
}
}
#endregion ILog
#region IDisposable
private bool _disposed = false;
public void Dispose()
{
foreach (var tk in _taskKeys)
{
Sheduller.Remove(tk);
}
if (false == _disposed)
{
_disposed = true;
CloseCurrentWriter();
}
}
#endregion IDisposable
}
public sealed class FileLogger : ILogger
{
#region Fields
/// <summary>
/// Stream to output to file
/// </summary>
private TextWriter _writer;
public static readonly Encoding DEFAULT_ENCODING = Encoding.UTF8;
#endregion Fields
#region Ctors
public FileLogger(string path)
{
CreateLogFile(PreparePath(path));
}
#endregion Ctors
#region Private member
private static void PrepareFolder(string path)
{
if (Directory.Exists(path) == false)
{
Directory.CreateDirectory(path);
}
}
private static string PreparePath(string path)
{
if (path.IndexOf(':') == -1)
{
path = Path.Combine(Configuration.BaseDirectory, path);
}
path = FSUtils.PathCorrection(path);
PrepareFolder(Path.GetDirectoryName(path));
return path;
}
/// <summary>
/// Closing the current log
/// </summary>
private void CloseCurrentWriter()
{
if (_writer != null!)
{
_writer.Flush();
_writer.Close();
_writer.Dispose();
_writer = null!;
}
}
private void CreateLogFile(string path)
{
Stream stream = null!;
try
{
stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
_writer = new StreamWriter(stream, DEFAULT_ENCODING);
stream = null!;
}
catch
{
if (stream != null!)
{
stream.Dispose();
}
throw;
}
}
#endregion Private member
#region ILog
public void Write(LogLevel level, string message)
{
if (false == _disposed)
{
if (level == LogLevel.Raw)
{
_writer.WriteLine(message);
}
else
{
var meta = string.Format("[{0:dd'.'MM'.'yyyy HH':'mm':'ss} {1}]\t", DateTime.Now, LogLevelNameMapping.CompactName(level));
_writer.Write(meta);
_writer.WriteLine(message);
}
_writer.Flush();
}
}
#endregion ILog
#region IDisposable
private bool _disposed = false;
public void Dispose()
{
if (false == _disposed)
{
_disposed = true;
CloseCurrentWriter();
}
}
#endregion IDisposable
}
}

Powered by TurnKey Linux.