initial
This commit is contained in:
31
code/portaloggy.sln
Normal file
31
code/portaloggy.sln
Normal 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
|
||||
15
code/portaloggy/Constants.cs
Normal file
15
code/portaloggy/Constants.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
52
code/portaloggy/GenericPrettifier.cs
Normal file
52
code/portaloggy/GenericPrettifier.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
code/portaloggy/ILogger.cs
Normal file
24
code/portaloggy/ILogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
112
code/portaloggy/LoggerExtensions.cs
Normal file
112
code/portaloggy/LoggerExtensions.cs
Normal 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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
37
code/portaloggy/Loggers/AggregatedLogger.cs
Normal file
37
code/portaloggy/Loggers/AggregatedLogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
code/portaloggy/Loggers/ConsoleLogger.cs
Normal file
111
code/portaloggy/Loggers/ConsoleLogger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
code/portaloggy/portaloggy.csproj
Normal file
14
code/portaloggy/portaloggy.csproj
Normal 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>
|
||||
Reference in New Issue
Block a user