This commit is contained in:
2024-08-03 17:43:33 +02:00
parent 7604d593c7
commit 380b847475
8 changed files with 396 additions and 0 deletions

31
code/portaloggy.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "portaloggy", "portaloggy\portaloggy.csproj", "{67A83ECB-9B90-47E1-B56A-BAFE8DB64B1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "portaloggy.Tester", "portaloggy.Tester\portaloggy.Tester.csproj", "{4BE8310F-EC55-443C-A5AF-3C2D065E314C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{67A83ECB-9B90-47E1-B56A-BAFE8DB64B1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67A83ECB-9B90-47E1-B56A-BAFE8DB64B1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67A83ECB-9B90-47E1-B56A-BAFE8DB64B1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67A83ECB-9B90-47E1-B56A-BAFE8DB64B1D}.Release|Any CPU.Build.0 = Release|Any CPU
{4BE8310F-EC55-443C-A5AF-3C2D065E314C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BE8310F-EC55-443C-A5AF-3C2D065E314C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BE8310F-EC55-443C-A5AF-3C2D065E314C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BE8310F-EC55-443C-A5AF-3C2D065E314C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EF3D422B-47DF-4283-A055-D1A59E923175}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,15 @@
namespace portaloggy
{
public class Constants
{
public const string LOG_SEVERITY_NONE = "NONE";
public const string LOG_SEVERITY_TRACE = "TRACE";
public const string LOG_SEVERITY_DEBUG = "DEBUG";
public const string LOG_SEVERITY_INFO = "INFO";
public const string LOG_SEVERITY_WARN = "WARN";
public const string LOG_SEVERITY_ERROR = "ERROR";
public const string LOG_SEVERITY_FATAL = "FATAL";
public const string LOG_SEVERITY_SUCCESS = "SUCCESS";
public const string LOG_SEVERITY_ANCHOR = "ANCHOR";
}
}

View File

@ -0,0 +1,52 @@
namespace portaloggy
{
public static class GenericPrettifier
{
public static string GetPrettifiedSeverity(string severity = null)
{
var calculatedSeverity = severity ?? Constants.LOG_SEVERITY_NONE;
if(calculatedSeverity.Length > 5)
{
calculatedSeverity = calculatedSeverity.Substring(0, 5);
}
return calculatedSeverity.PadLeft(5);
}
public static string GetPrettifiedSource(string knownCallerMemberName, string knownCallerFilePath, int knownCallerLineNumber, bool isFileNameToClassTypeTranslationEnabled = true)
{
var computedSource = string.Empty;
var sourceFile = new FileInfo(knownCallerFilePath);
if(isFileNameToClassTypeTranslationEnabled)
{
computedSource = $"{sourceFile.Name.Substring(0, sourceFile.Name.Length - sourceFile.Extension.Length)}";
}
else
{
computedSource = $"{sourceFile.Name}";
}
if(computedSource.Length>20)
{
computedSource = computedSource.Substring(computedSource.Length - 17);
computedSource = $"...{computedSource}";
}
computedSource = computedSource.PadRight(20);
if(knownCallerLineNumber > 0)
{
var formattedLineNumber = knownCallerLineNumber.ToString().PadRight(4);
computedSource = $"{computedSource}:{formattedLineNumber}";
}
else
{
computedSource = computedSource.PadRight(25);
}
return computedSource;
}
}
}

View File

@ -0,0 +1,24 @@
using System.Runtime.CompilerServices;
namespace portaloggy
{
/// <summary>
/// Universal logger. Use everywhere.
/// </summary>
public interface ILogger
{
/// <summary>
/// Logs a message.
/// </summary>
/// <param name="message">Your message.</param>
/// <param name="severity">Optionally specify severity.</param>
/// <param name="exception">Optionally give exception.</param>
void Log(
string message,
string severity = null,
Exception exception = null,
[CallerMemberName] string memberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0);
}
}

View File

@ -0,0 +1,112 @@
using System.Runtime.CompilerServices;
namespace portaloggy
{
public static class LoggerExtensions
{
/// <summary>
/// Trace messages are debug messages which are too many to even store.
/// </summary>
/// <remarks>
/// For example, you're checking a directory for new files every 5 seconds, and you generate 5 log message for
/// starting, running, ending and result of the operation. You run this over 5 directories. That's 25 messages
/// per 5 seconds. You don't need to log this unless you're really after some serious bug that you're hunting.
/// Designating this kind of log message as trace allows us to ignore it, as it would baloon our log files.
/// </remarks>
public static void Trace(
this ILogger logger,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_TRACE, null, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary>
/// Debug messages that are likely not going to be enabled/collected from a production build, or will be only
/// enabled temporarily to hunt down a bug. Use <see cref="Trace(ILogger, string, string, string, int)"/>, if
/// you're expecting to have so many messages, that it would make the log output basically unreadable.
/// </summary>
public static void Debug(
this ILogger logger,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_DEBUG, null, callerMemberName, callerFilePath, callerLineNumber);
}
public static void Info(
this ILogger logger,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_INFO, null, callerMemberName, callerFilePath, callerLineNumber);
}
public static void Warning(
this ILogger logger,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_WARN, null, callerMemberName, callerFilePath, callerLineNumber);
}
public static void Success(
this ILogger logger,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_SUCCESS, null, callerMemberName, callerFilePath, callerLineNumber);
}
public static void Error(
this ILogger logger,
string message,
Exception ex = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_ERROR, ex, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary>
/// Irrecoverable error after which the process or application will die.
/// </summary>
public static void Fatal(
this ILogger logger,
string message,
Exception ex = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
logger.Log(message, Constants.LOG_SEVERITY_FATAL, ex, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary>
/// Creates a log line that carries keywords, such as #specificScenario, to allow machine-readable identification
/// of log line in log output. Your message value is prefixed with a ":::".
/// </summary>
public static void Anchor(
this ILogger logger,
string message,
params string[] keywords)
{
try
{
logger.Log($"Anchored log line {string.Join(", ", keywords)}:::{message}", Constants.LOG_SEVERITY_ANCHOR);
}
catch { }
}
}
}

View File

@ -0,0 +1,37 @@
using System.Runtime.CompilerServices;
namespace portaloggy
{
/// <summary>
/// Allows logging into multiple loggers simultaneously.
/// </summary>
public class AggregatedLogger : ILogger
{
private readonly ICollection<ILogger> loggers;
public AggregatedLogger(params ILogger[] loggers)
{
if (loggers == null)
{
throw new ArgumentNullException(nameof(loggers));
}
else if (loggers.Length == 0)
{
throw new ArgumentException(nameof(loggers));
}
this.loggers = loggers;
}
public void Log(string message, string severity = null, Exception exception = null, [CallerMemberName] string memberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
lock(this.loggers)
{
foreach(ILogger logger in this.loggers)
{
logger.Log(message, severity, exception, memberName, callerFilePath, sourceLineNumber);
}
}
}
}
}

View File

@ -0,0 +1,111 @@
using System.Runtime.CompilerServices;
namespace portaloggy
{
public class ConsoleLogger : ILogger
{
/// <summary>
/// If enabled, logger will print [TRACE] logs. Warning: There are supposed to be A LOT of those.
/// </summary>
public bool IsTraceOutputEnabled = false;
/// <summary>
/// If enabled, logger will print [DEBUG] logs.
/// </summary>
public bool IsDebugOutputEnabled = true;
/// <summary>
/// If nabled, source file names will be trated as class types.
/// </summary>
public bool IsFileNameToClassTypeTranslationEnabled = true;
/// <summary>
/// If enabled, messages will use different coloring based on their severity.
/// </summary>
public bool IsMessageSeverityColoringEnabled = true;
private object printLocker = new object();
public void Log(string message, string severity = null, Exception exception = null, [CallerMemberName] string memberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
try
{
if (severity == Constants.LOG_SEVERITY_TRACE && this.IsTraceOutputEnabled == false)
{
return;
}
else if (severity == Constants.LOG_SEVERITY_DEBUG && this.IsDebugOutputEnabled == false)
{
return;
}
var processedMessage = exception == null
? message
: $"{exception.GetType().Name}: {exception.Message}";
if (exception != null && !string.IsNullOrWhiteSpace(message))
{
processedMessage = $"{message} Error: {processedMessage}";
}
this.Print(severity, $"[{DateTime.Now:s}] - {GenericPrettifier.GetPrettifiedSource(memberName, callerFilePath, sourceLineNumber, this.IsFileNameToClassTypeTranslationEnabled)} - {GenericPrettifier.GetPrettifiedSeverity(severity)} - {processedMessage ?? "No message."}");
if (exception != null)
{
var stacktrace = this.FormatStacktrace(exception);
if (string.IsNullOrWhiteSpace(stacktrace) == false)
{
Console.WriteLine("--- STACKTRACE ---");
Console.WriteLine(stacktrace);
}
}
}
catch { } // Ignore. We do not crash because of a failed log.
}
private void Print(string severity, string message)
{
lock (this.printLocker)
{
if (this.IsMessageSeverityColoringEnabled == false)
{
Console.WriteLine(message);
}
else if (severity == Constants.LOG_SEVERITY_TRACE)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
}
else if(severity == Constants.LOG_SEVERITY_DEBUG)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
}
else if(severity == Constants.LOG_SEVERITY_WARN)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else if(severity == Constants.LOG_SEVERITY_ERROR)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if(severity == Constants.LOG_SEVERITY_FATAL)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if(severity == Constants.LOG_SEVERITY_SUCCESS)
{
Console.ForegroundColor = ConsoleColor.Green;
}
Console.WriteLine(message);
Console.ResetColor();
}
}
private string FormatStacktrace(Exception ex)
{
var trace = $"\n{ex.StackTrace}";
return trace;
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Title>portaloggy</Title>
<Authors>mc</Authors>
<AssemblyVersion>1.0.1</AssemblyVersion>
<Version>$(AssemblyVersion)</Version>
<Description>A highly-portable, multi-platform, system-agnostic logging abstraction. Use portaloggy.ILogger everywhere. Make use of portaloggy.LoggerExtensions. Already contains ConsoleLogger for dead-simple console logging and AggregatedLogger for simultaneous logging to your own implementation of ILogger.</Description>
<Copyright>mc @ 2024</Copyright>
</PropertyGroup>
</Project>