Upload
This commit is contained in:
Executable
+3
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="NETLogger/NETLogger.csproj" />
|
||||
</Solution>
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NETLogger.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that allows for the registration of loggers and controls their enabled state.
|
||||
/// </summary>
|
||||
public interface ILoggerControlService
|
||||
{
|
||||
bool IsEnabled { get; set; }
|
||||
|
||||
IDisposable Register(string name, Action<bool> setEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A service that allows for the registration of loggers and controls their enabled state.
|
||||
/// </summary>
|
||||
public sealed class LoggerControlService : ILoggerControlService
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly Dictionary<Guid, Registration> _registrations = new Dictionary<Guid, Registration>();
|
||||
|
||||
private bool _isEnabled = true;
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get { lock (_syncRoot) return _isEnabled; }
|
||||
set
|
||||
{
|
||||
Action<bool>[] setters;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isEnabled == value) return;
|
||||
|
||||
_isEnabled = value;
|
||||
setters = _registrations.Values.Select(x => x.SetEnabled).ToArray();
|
||||
}
|
||||
|
||||
foreach (Action<bool> setter in setters) setter(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a logger with the control service, allowing it to be enabled or disabled.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="setEnabled"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public IDisposable Register(string name, Action<bool> setEnabled)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Logger name is required.", nameof(name));
|
||||
if (setEnabled == null) throw new ArgumentNullException(nameof(setEnabled));
|
||||
|
||||
var id = Guid.NewGuid();
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_registrations.Add(id, new Registration(name, setEnabled));
|
||||
setEnabled(_isEnabled);
|
||||
}
|
||||
|
||||
return new UnregisterAction(() =>
|
||||
{
|
||||
lock (_syncRoot) _registrations.Remove(id);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class Registration
|
||||
{
|
||||
public Registration(string name, Action<bool> setEnabled)
|
||||
{
|
||||
Name = name;
|
||||
SetEnabled = setEnabled;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public Action<bool> SetEnabled { get; }
|
||||
}
|
||||
|
||||
private sealed class UnregisterAction : IDisposable
|
||||
{
|
||||
private Action _unregister;
|
||||
|
||||
public UnregisterAction(Action unregister) => _unregister = unregister;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var unregister = _unregister;
|
||||
_unregister = null;
|
||||
unregister?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and registers a logger of type T with the provided ILoggerControlService.
|
||||
/// The logger's enabled state will be controlled by the ILoggerControlService.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="loggerControlService"></param>
|
||||
/// <param name="registration"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static ILogger<T> CreateAndRegisterNETLogger<T>(ILoggerControlService loggerControlService, out IDisposable registration)
|
||||
{
|
||||
if (loggerControlService == null) throw new ArgumentNullException(nameof(loggerControlService));
|
||||
|
||||
var logger = new Logger<T>();
|
||||
|
||||
registration = loggerControlService.Register(typeof(T).FullName, isEnabled =>
|
||||
{
|
||||
if (isEnabled) logger.Enable();
|
||||
else logger.Disable();
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+223
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace NETLogger
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5
|
||||
}
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
void Log(LogLevel level, string message, Exception ex = null);
|
||||
void Trace(string message);
|
||||
void Debug(string message);
|
||||
void Info(string message);
|
||||
void Warn(string message, Exception ex = null);
|
||||
void Error(string message, Exception ex = null);
|
||||
void Fatal(string message, Exception ex = null);
|
||||
}
|
||||
|
||||
public interface ILogger<T> : ILogger { }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for all Logger Instances.
|
||||
/// This class is static and should be configured at application startup.
|
||||
/// </summary>
|
||||
public static class LogConfiguration
|
||||
{
|
||||
public static LogLevel MinimumLevel { get; set; } = LogLevel.Debug;
|
||||
public static bool EnableDebugView { get; set; } = true;
|
||||
public static bool EnableConsole { get; set; } = false;
|
||||
|
||||
public static string LogFilePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new log file path based on the current timestamp in the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="directory"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetNewLogFilePath(string directory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(directory)) return null;
|
||||
|
||||
var fileName = $"log_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
|
||||
return Path.Combine(directory, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the log file path. If the directory does not exist, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public static void SetLogFile(string path)
|
||||
{
|
||||
LogFilePath = path;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to get a new log file path and set it as the current log file
|
||||
/// </summary>
|
||||
/// <param name="directory"></param>
|
||||
public static void GetAndSetNewLogFile(string directory) => SetLogFile(GetNewLogFilePath(directory));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default logging path in the user's application data folder for the specified application name.
|
||||
/// </summary>
|
||||
/// <param name="appName"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static string CreateAndGetDefaultLoggingPath(string appName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(appName)) throw new ArgumentException("Application name is required.", nameof(appName));
|
||||
var dir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName, "Logs");
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static void RemoveLoggingPath()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LogFilePath))
|
||||
{
|
||||
try { File.Delete(LogFilePath); }
|
||||
catch { /* silently ignore */ }
|
||||
LogFilePath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LogWriter
|
||||
{
|
||||
private static readonly object _fileLock = new object();
|
||||
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
// P/Invoke for Windows OutputDebugString (DbgView64)
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "OutputDebugStringW")]
|
||||
private static extern void OutputDebugString(string lpOutputString);
|
||||
|
||||
internal static void Write(LogLevel level, string category, string message, Exception ex)
|
||||
{
|
||||
if (level < LogConfiguration.MinimumLevel) return;
|
||||
|
||||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
string levelStr = level.ToString().ToUpperInvariant();
|
||||
string fullMessage = ex == null ? $"[{timestamp}] [{levelStr}] [{category}] {message}" : $"[{timestamp}] [{levelStr}] [{category}] {message} --> Exception: {ex}";
|
||||
|
||||
if (LogConfiguration.EnableDebugView && IsWindows) OutputDebugString(fullMessage + Environment.NewLine);
|
||||
if (LogConfiguration.EnableConsole) Console.WriteLine(fullMessage);
|
||||
if (!string.IsNullOrEmpty(LogConfiguration.LogFilePath))
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
try { File.AppendAllText(LogConfiguration.LogFilePath, fullMessage + Environment.NewLine, Encoding.UTF8); }
|
||||
catch
|
||||
{
|
||||
// logging should under no circumstances crash the application, so we silently ignore any exceptions here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for clearing log files in the log directory.
|
||||
/// </summary>
|
||||
internal class LogClearer
|
||||
{
|
||||
public static string DirectoryPath => Directory.GetParent(LogConfiguration.LogFilePath).FullName;
|
||||
|
||||
/// <summary>
|
||||
/// Clears all log files in the log directory.
|
||||
/// </summary>
|
||||
public static void ClearDirectory()
|
||||
{
|
||||
var dir = DirectoryPath;
|
||||
if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(dir))
|
||||
{
|
||||
try { File.Delete(file); }
|
||||
catch { /* silently ignore */ }
|
||||
}
|
||||
}
|
||||
catch { /* silently ignore */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears old log files in the log directory, keeping only the specified number of most recent logs.
|
||||
/// </summary>
|
||||
/// <param name="logsToKeep"></param>
|
||||
public static void ClearOldLogs(int logsToKeep = 30)
|
||||
{
|
||||
var dir = DirectoryPath;
|
||||
if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
var logFiles = new DirectoryInfo(dir).GetFiles("log_*.log");
|
||||
Array.Sort(logFiles, (x, y) => y.CreationTime.CompareTo(x.CreationTime));
|
||||
|
||||
for (int i = logsToKeep; i < logFiles.Length; i++)
|
||||
{
|
||||
try { logFiles[i].Delete(); }
|
||||
catch { /* silently ignore */ }
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ /* silently ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// typed generic logger class that implements ILogger<T> interface
|
||||
/// </summary>
|
||||
public class Logger<T> : ILogger<T>
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
|
||||
public bool Enabled { get; private set; } = true;
|
||||
|
||||
public Logger() => _categoryName = typeof(T).Name;
|
||||
public Logger(string customCategory) => _categoryName = customCategory;
|
||||
|
||||
public void Enable() => Enabled = true;
|
||||
public void Disable() => Enabled = false;
|
||||
|
||||
public void Log(LogLevel level, string message, Exception ex = null)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
LogWriter.Write(level, _categoryName, message, ex);
|
||||
}
|
||||
|
||||
public void Trace(string message) => Log(LogLevel.Trace, message);
|
||||
public void Debug(string message) => Log(LogLevel.Debug, message);
|
||||
public void Info(string message) => Log(LogLevel.Info, message);
|
||||
public void Warn(string message, Exception ex = null) => Log(LogLevel.Warn, message, ex);
|
||||
public void Error(string message, Exception ex = null) => Log(LogLevel.Error, message, ex);
|
||||
public void Fatal(string message, Exception ex = null) => Log(LogLevel.Fatal, message, ex);
|
||||
|
||||
public void ClearLogs()
|
||||
{
|
||||
LogClearer.ClearDirectory();
|
||||
Info("Clearing all log files in the log directory.");
|
||||
}
|
||||
|
||||
public void ClearOldLogs(int logsToKeep = 30)
|
||||
{
|
||||
LogClearer.ClearOldLogs(logsToKeep);
|
||||
Info($"Clearing old log files in the log directory, keeping only the {logsToKeep} most recent logs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.1.0</Version>
|
||||
<Authors>Timo Niemann</Authors>
|
||||
<Company>Timo Niemann</Company>
|
||||
<Description>NETLogger is a logging library for .NET applications. With support for multiple logging providers and easy configuration, it simplifies the process of adding robust logging to your app.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,167 @@
|
||||
# NETLogger
|
||||
|
||||
NETLogger is a small logging library for .NET applications targeting .NET Standard 2.0.
|
||||
|
||||
## Installation
|
||||
|
||||
Add `NETLogger/NETLogger.csproj` as a project reference or install the NuGet package generated during the build.
|
||||
|
||||
## Configure the application
|
||||
|
||||
Configure the global logger settings once during application startup. The following example stores log files in the current user's application data directory:
|
||||
|
||||
```csharp
|
||||
using NETLogger;
|
||||
|
||||
LogConfiguration.MinimumLevel = LogLevel.Info;
|
||||
LogConfiguration.EnableConsole = true;
|
||||
LogConfiguration.EnableDebugView = true; // Windows only
|
||||
|
||||
string logDirectory = LogConfiguration.CreateAndGetDefaultLoggingPath("MyApplication");
|
||||
LogConfiguration.GetAndSetNewLogFile(logDirectory);
|
||||
```
|
||||
|
||||
Alternatively, use `LogConfiguration.SetLogFile(path)` to specify a complete file path. Without a configured file path, NETLogger writes only to the enabled console and Windows debug outputs.
|
||||
|
||||
## Use a logger directly
|
||||
|
||||
For an independent class, create a typed `Logger<T>`. The type name is automatically included in each log entry as its category:
|
||||
|
||||
```csharp
|
||||
public class Service
|
||||
{
|
||||
private readonly ILogger _logger = new Logger<Service>();
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_logger.Info("Service started.");
|
||||
|
||||
try
|
||||
{
|
||||
// Application code
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error("Service failed.", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A directly created `Logger<T>` can be disabled and enabled independently with `Disable()` and `Enable()`. `ClearOldLogs(logsToKeep)` removes old log files; without an argument, it keeps the 30 most recent files. `ClearLogs()` removes all log files from the configured directory.
|
||||
|
||||
## Control multiple loggers centrally
|
||||
|
||||
Use a shared `LoggerControlService` when multiple loggers need to be enabled or disabled together. An owning class creates the service and registers its logger:
|
||||
|
||||
```csharp
|
||||
using NETLogger;
|
||||
using NETLogger.Services;
|
||||
|
||||
public sealed class ApplicationController : IDisposable
|
||||
{
|
||||
private readonly ILoggerControlService _loggerControlService;
|
||||
private readonly ILogger<ApplicationController> _logger;
|
||||
private readonly IDisposable _loggerRegistration;
|
||||
|
||||
public ILoggerControlService LoggerControlService => _loggerControlService;
|
||||
|
||||
public ApplicationController() : this(new LoggerControlService()) { }
|
||||
|
||||
public ApplicationController(ILoggerControlService loggerControlService)
|
||||
{
|
||||
_loggerControlService = loggerControlService
|
||||
?? throw new ArgumentNullException(nameof(loggerControlService));
|
||||
|
||||
_logger = NETLogger.Services.LoggerControlService.CreateAndRegisterNETLogger<ApplicationController>(
|
||||
_loggerControlService,
|
||||
out _loggerRegistration);
|
||||
|
||||
_logger.Info("Application controller initialized.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggerRegistration.Dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dependent classes receive the same service and register their own typed logger. Dispose the `IDisposable` registration when the owning instance is disposed:
|
||||
|
||||
```csharp
|
||||
public sealed class BackgroundService : IDisposable
|
||||
{
|
||||
private readonly ILogger<BackgroundService> _logger;
|
||||
private readonly IDisposable _loggerRegistration;
|
||||
|
||||
public BackgroundService(ILoggerControlService loggerControlService)
|
||||
{
|
||||
_logger = LoggerControlService.CreateAndRegisterNETLogger<BackgroundService>(
|
||||
loggerControlService,
|
||||
out _loggerRegistration);
|
||||
|
||||
_logger.Info("Background service initialized.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggerRegistration.Dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All registered loggers can be toggled together:
|
||||
|
||||
```csharp
|
||||
loggerControlService.IsEnabled = false; // Disable all registered loggers
|
||||
loggerControlService.IsEnabled = true; // Enable all registered loggers
|
||||
```
|
||||
|
||||
## Initialize loggers for static members
|
||||
|
||||
Classes with static logging members can use explicit initialization to participate in the shared `LoggerControlService`:
|
||||
|
||||
```csharp
|
||||
public sealed class ConfigurationCache
|
||||
{
|
||||
private static ILogger<ConfigurationCache> _logger;
|
||||
private static IDisposable _loggerRegistration;
|
||||
|
||||
private ConfigurationCache() { }
|
||||
|
||||
public static void InitializeLogger(ILoggerControlService loggerControlService)
|
||||
{
|
||||
if (loggerControlService == null)
|
||||
throw new ArgumentNullException(nameof(loggerControlService));
|
||||
|
||||
_loggerRegistration?.Dispose();
|
||||
_logger = LoggerControlService.CreateAndRegisterNETLogger<ConfigurationCache>(
|
||||
loggerControlService,
|
||||
out _loggerRegistration);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The owning class initializes the logger with its shared service:
|
||||
|
||||
```csharp
|
||||
ConfigurationCache.InitializeLogger(_loggerControlService);
|
||||
```
|
||||
|
||||
## Log levels and outputs
|
||||
|
||||
The available levels are `Trace`, `Debug`, `Info`, `Warn`, `Error`, and `Fatal`. Messages below `LogConfiguration.MinimumLevel` are ignored. `Warn`, `Error`, and `Fatal` optionally accept an exception:
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
// Application code
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error("Operation failed.", exception);
|
||||
}
|
||||
```
|
||||
|
||||
NETLogger can write to the Windows debug output, the console, and a UTF-8 log file at the same time.
|
||||
Reference in New Issue
Block a user