Showing preview only (218K chars total). Download the full file or copy to clipboard to get everything.
Repository: NimaAra/Easy.Logger
Branch: master
Commit: 8a5c1c61dc0e
Files: 49
Total size: 203.2 KB
Directory structure:
gitextract_ar9s1khg/
├── .gitattributes
├── .gitignore
├── Easy.Logger/
│ ├── AsyncBufferingForwardingAppender.cs
│ ├── Easy.Logger.csproj
│ ├── Log4NetLogger.cs
│ ├── Log4NetService.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Scope.cs
│ ├── Sequencer.cs
│ └── sample-log4net.config
├── Easy.Logger.Benchmarker/
│ ├── App.config
│ ├── Easy.Logger.Benchmarker.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── the-log4net.config
├── Easy.Logger.Benchmarker.Core/
│ ├── Easy.Logger.Benchmarker.Core.csproj
│ ├── Program.cs
│ └── log4net.config
├── Easy.Logger.Extensions/
│ ├── Easy.Logger.Extensions.csproj
│ ├── HTTPAppender.cs
│ └── Properties/
│ └── AssemblyInfo.cs
├── Easy.Logger.Extensions.Microsoft/
│ ├── Easy.Logger.Extensions.Microsoft.csproj
│ ├── EasyLogger.cs
│ ├── EasyLoggerConfig.cs
│ ├── EasyLoggerProvider.cs
│ ├── IEasyLoggerConfig.cs
│ ├── LoggingBuilderExtensions.cs
│ └── README.md
├── Easy.Logger.Interfaces/
│ ├── Easy.Logger.Interfaces.csproj
│ ├── IEasyLogger.cs
│ └── ILogService.cs
├── Easy.Logger.Tests.Integration/
│ ├── Context.cs
│ ├── Easy.Logger.Tests.Integration.csproj
│ ├── EasyLogListener.cs
│ ├── Models/
│ │ └── LogPayload.cs
│ └── integration.tests-log4net.config
├── Easy.Logger.Tests.Unit/
│ ├── AsyncBufferingForwardingAppenderTests.cs
│ ├── CreatingNewLog4NetServiceTests.cs
│ ├── Easy.Logger.Tests.Unit.csproj
│ ├── HTTPAppenderTests.cs
│ ├── Helpers/
│ │ └── ProcessHelper.cs
│ └── Log4NetLoggerTests.cs
├── Easy.Logger.sln
├── LICENSE
├── NuGet-Pack-Easy.Logger.Extensions.Microsoft.bat
├── NuGet-Pack-Easy.Logger.Extensions.bat
├── NuGet-Pack-Easy.Logger.Interfaces.bat
├── NuGet-Pack-Easy.Logger.bat
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
*.publishproj
# NuGet Packages
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
================================================
FILE: Easy.Logger/AsyncBufferingForwardingAppender.cs
================================================
namespace Easy.Logger
{
using System;
using System.Threading;
using log4net.Appender;
using log4net.Core;
using log4net.Util;
/// <summary>
/// An appender which batches the log events and asynchronously forwards them to any configured appenders.
/// <seealso href="www.nimaara.com/2016/01/01/high-performance-logging-log4net/"/>
/// </summary>
public sealed class AsyncBufferingForwardingAppender : BufferingForwardingAppender
{
private const int DEFAULT_IDLE_TIME = 500;
private readonly Sequencer<LoggingEvent[]> _sequencer;
private TimeSpan _idleTimeThreshold;
private Timer _idleFlushTimer;
private DateTime _lastFlushTime;
private bool IsIdle
{
get
{
if (DateTime.UtcNow - _lastFlushTime >= _idleTimeThreshold) { return true; }
return false;
}
}
/// <summary>
/// Gets or sets the idle-time in milliseconds at which any pending logging events are flushed.
/// <value>The idle-time in milliseconds.</value>
/// <remarks>
/// <para>
/// The value should be a positive integer representing the maximum idle-time of logging events
/// to be collected in the <see cref="AsyncBufferingForwardingAppender"/>. When this value is
/// reached, buffered events are then flushed. By default the idle-time is <c>500</c> milliseconds.
/// </para>
/// <para>
/// If the <see cref="IdleTime"/> is set to a value less than or equal to <c>0</c>
/// then use the default value is used.
/// </para>
/// </remarks>
/// </summary>
public int IdleTime { get; set; } = DEFAULT_IDLE_TIME;
/// <summary>
/// Creates an instance of the <see cref="AsyncBufferingForwardingAppender"/>
/// </summary>
public AsyncBufferingForwardingAppender()
{
_sequencer = new Sequencer<LoggingEvent[]>(Process);
_sequencer.OnException += (sender, args)
=> LogLog.Error(GetType(), "An exception occurred while processing LogEvents.", args.Exception);
}
/// <summary>
/// Activates the options for this appender.
/// </summary>
public override void ActivateOptions()
{
base.ActivateOptions();
LogWarningIfLossy();
if (IdleTime <= 0) { IdleTime = DEFAULT_IDLE_TIME; }
_idleTimeThreshold = TimeSpan.FromMilliseconds(IdleTime);
_idleFlushTimer = new Timer(InvokeFlushIfIdle, null, _idleTimeThreshold, _idleTimeThreshold);
}
/// <summary>
/// Forwards the events to every configured appender.
/// </summary>
/// <param name="events">The events that need to be forwarded</param>
protected override void SendBuffer(LoggingEvent[] events)
{
if (!_sequencer.ShutdownRequested)
{
_sequencer.Enqueue(events);
} else
{
base.SendBuffer(events);
}
}
/// <summary>
/// Ensures that all pending logging events are flushed out before exiting.
/// </summary>
protected override void OnClose()
{
_idleFlushTimer?.Dispose();
_sequencer.Shutdown();
Flush();
base.OnClose();
}
private void Process(LoggingEvent[] logEvents)
{
base.SendBuffer(logEvents);
_lastFlushTime = DateTime.UtcNow;
}
/// <summary>
/// This only flushes if <see cref="BufferingAppenderSkeleton.Lossy"/> is <c>False</c>.
/// </summary>
private void InvokeFlushIfIdle(object _)
{
if (!IsIdle) { return; }
if (_sequencer.ShutdownRequested) { return; }
Flush();
}
private void LogWarningIfLossy()
{
if (!Lossy) { return; }
var warning = new LoggingEvent(new LoggingEventData
{
Level = Level.Warn,
LoggerName = GetType().Name,
ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),
TimeStampUtc = DateTime.UtcNow,
Message = "This is a 'lossy' appender therefore log messages may be dropped."
});
Lossy = false;
Append(warning);
Flush();
Lossy = true;
}
}
}
================================================
FILE: Easy.Logger/Easy.Logger.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Easy.Logger</PackageId>
<Description>A modern high performance cross platform wrapper around Log4Net.</Description>
<Authors>Nima Ara</Authors>
<Copyright>2024 Nima Ara</Copyright>
<PackageTags>log4net;Logging;Easy;Logger;Log</PackageTags>
<PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>-</PackageReleaseNotes>
<RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard1.3;net48</TargetFrameworks>
<AssemblyTitle>Easy Logger</AssemblyTitle>
<AssemblyName>Easy.Logger</AssemblyName>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Easy.Logger.Interfaces" Version="4.0.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<DefineConstants>$(DefineConstants);NET_STANDARD</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.Debug" Version="4.3.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
================================================
FILE: Easy.Logger/Log4NetLogger.cs
================================================
namespace Easy.Logger
{
using System;
using System.Diagnostics;
using System.Globalization;
using Easy.Logger.Interfaces;
using log4net;
using log4net.Core;
using log4net.Util;
/// <summary>
/// A <c>log4net</c> implementation of the <see cref="IEasyLogger{T}"/> interface.
/// </summary>
public sealed class Log4NetLogger<T> : Log4NetLogger, IEasyLogger<T>
{
// ReSharper disable once AssignmentIsFullyDiscarded
static Log4NetLogger() => _ = Log4NetService.Instance;
/// <summary>
/// Creates an instance of the <see cref="Log4NetLogger{T}"/> where the name of the logger is
/// set as the name of the type of <typeparamref name="T"/>.
/// </summary>
public Log4NetLogger() : base(LogManager.GetLogger(typeof(T))) {}
}
/// <summary>
/// A <c>log4net</c> implementation of the <see cref="ILogger"/> interface.
/// </summary>
public class Log4NetLogger : IEasyLogger
{
private static readonly Type ThisDeclaringType = typeof(Log4NetLogger);
private readonly ILog _logger;
/// <summary>
/// Creates an instance of the <see cref="Log4NetLogger"/>.
/// </summary>
/// <param name="logger"></param>
protected internal Log4NetLogger(ILog logger) => _logger = logger;
/// <summary>
/// Gets the logger name.
/// </summary>
public string Name => _logger.Logger.Name;
/// <summary>
/// Returns an <see cref="IDisposable"/> which allows the caller to specify a scope as
/// <paramref name="name"/> which will then be rendered as part of the message.
/// </summary>
/// <param name="name">The name of the scope</param>
public IDisposable GetScopedLogger(string name) => new Scope(name);
#region Levels Enabled
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Trace</c> messages.
/// </summary>
public bool IsTraceEnabled => IsEnabledFor(Level.Trace);
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Debug</c> messages.
/// </summary>
public bool IsDebugEnabled => _logger.IsDebugEnabled;
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Info</c> messages.
/// </summary>
public bool IsInfoEnabled => _logger.IsInfoEnabled;
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Warn</c> messages.
/// </summary>
public bool IsWarnEnabled => _logger.IsWarnEnabled;
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Error</c> messages.
/// </summary>
public bool IsErrorEnabled => _logger.IsErrorEnabled;
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Fatal</c> messages.
/// </summary>
public bool IsFatalEnabled => _logger.IsFatalEnabled;
#endregion
#region Trace
/// <summary>
/// Logs a message object with the <c>Trace</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Debug</c> enabled by comparing the level of
/// this logger with the <c>Trace</c> level. If this logger is <c>Debug</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Debug(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Trace(object message) => LogImpl(Level.Trace, message, null);
/// <summary>
/// Logs a message object with the <c>Trace</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Trace(object message, Exception exception) => LogImpl(Level.Trace, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Trace</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void TraceFormat(string format, object arg)
=> LogImpl(Level.Trace, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Trace</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void TraceFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Trace,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Trace</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void TraceFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Trace,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Trace</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void TraceFormat(string format, params object[] args)
=> LogImpl(
Level.Trace,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Trace</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void TraceFormat(IFormatProvider provider, string format, params object[] args)
=> LogImpl(
Level.Trace,
new SystemStringFormat(provider, format, args),
null);
#endregion
#region Debug
/// <summary>
/// Logs a message object with the <c>Debug</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Debug</c> enabled by comparing the level of
/// this logger with the <c>Debug</c> level. If this logger is <c>Debug</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Debug(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Debug(object message) => LogImpl(Level.Debug, message, null);
/// <summary>
/// Logs a message object with the <c>Debug</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Debug(object message, Exception exception) => LogImpl(Level.Debug, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Debug</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void DebugFormat(string format, object arg)
=> LogImpl(Level.Debug, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Debug</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void DebugFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Debug,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Debug</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void DebugFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Debug,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Debug</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void DebugFormat(string format, params object[] args)
=> LogImpl(
Level.Debug,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Debug</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Debug(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void DebugFormat(IFormatProvider provider, string format, params object[] args)
=> LogImpl(
Level.Debug,
new SystemStringFormat(provider, format, args),
null);
#endregion
#region Info
/// <summary>
/// Logs a message object with the <c>Info</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Info</c> enabled by comparing the level of
/// this logger with the <c>Info</c> level. If this logger is <c>Info</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Info(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Info(object message) => LogImpl(Level.Info, message, null);
/// <summary>
/// Logs a message object with the <c>Info</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Info(object message, Exception exception) => LogImpl(Level.Info, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Info</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Info(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void InfoFormat(string format, object arg)
=> LogImpl(Level.Info, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Info</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Info(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void InfoFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Info,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Info</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Info(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void InfoFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Info,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Info</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Info(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void InfoFormat(string format, params object[] args)
=> LogImpl(
Level.Info,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Info</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Info(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void InfoFormat(IFormatProvider provider, string format, params object[] args)
=> LogImpl(
Level.Info,
new SystemStringFormat(provider, format, args),
null);
#endregion
#region Warn
/// <summary>
/// Logs a message object with the <c>Warn</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Warn</c> enabled by comparing the level of
/// this logger with the <c>Warn</c> level. If this logger is <c>Warn</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Warn(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Warn(object message) => LogImpl(Level.Warn, message, null);
/// <summary>
/// Logs a message object with the <c>Warn</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Warn(object message, Exception exception) => LogImpl(Level.Warn, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Warn</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Warn(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void WarnFormat(string format, object arg)
=> LogImpl(Level.Warn, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Warn</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Warn(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void WarnFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Warn,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Warn</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Warn(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void WarnFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Warn,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Warn</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Warn(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void WarnFormat(string format, params object[] args)
=> LogImpl(
Level.Warn,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Warn</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Warn(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void WarnFormat(IFormatProvider provider, string format, params object[] args) => LogImpl(
Level.Warn,
new SystemStringFormat(provider, format, args),
null);
#endregion
#region Error
/// <summary>
/// Logs a message object with the <c>Error</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Error</c> enabled by comparing the level of
/// this logger with the <c>Error</c> level. If this logger is <c>Error</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Error(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Error(object message) => LogImpl(Level.Error, message, null);
/// <summary>
/// Logs a message object with the <c>Error</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Error(object message, Exception exception) => LogImpl(Level.Error, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Error</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Error(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void ErrorFormat(string format, object arg)
=> LogImpl(Level.Error, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Error</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Error(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void ErrorFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Error,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Error</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Error(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void ErrorFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Error,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Error</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Error(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void ErrorFormat(string format, params object[] args)
=> LogImpl(
Level.Error,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Error</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Error(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void ErrorFormat(IFormatProvider provider, string format, params object[] args)
=> LogImpl(
Level.Error,
new SystemStringFormat(provider, format, args),
null);
#endregion
#region Fatal
/// <summary>
/// Logs a message object with the <c>Fatal</c> level.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <remarks>
/// <para>
/// This method first checks if this logger is <c>Fatal</c> enabled by comparing the level of
/// this logger with the <c>Fatal</c> level. If this logger is <c>Fatal</c>
/// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate
/// <see cref="T:log4net.ObjectRenderer.IObjectRenderer"/>. It then proceeds to call all the registered appenders
/// in this logger and also higher in the hierarchy depending on the value of the additivity flag.
/// </para>
/// <para>
/// <b>WARNING</b> Note that passing an<see cref="T:System.Exception"/> to this method
/// will print the name of the <see cref="T:System.Exception"/> but no stack trace.
/// To print a stack trace use the <see cref="M:Fatal(object,Exception)"/> form instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void Fatal(object message) => LogImpl(Level.Fatal, message, null);
/// <summary>
/// Logs a message object with the <c>Fatal</c> level including
/// the stack trace of the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
[DebuggerStepThrough]
public void Fatal(object message, Exception exception) => LogImpl(Level.Fatal, message, exception);
/// <summary>
/// Logs a formatted message string with the <c>Fatal</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method.
/// See <see cref = "M:String.Format(string, object[])" /> for details
/// of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Fatal(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void FatalFormat(string format, object arg)
=> LogImpl(Level.Fatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);
/// <summary>
/// Logs a formatted message string with the <c>Fatal</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Fatal(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void FatalFormat(string format, object arg1, object arg2)
=> LogImpl(
Level.Fatal,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2),
null);
/// <summary>
/// Logs a formatted message string with the <c>Fatal</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
/// <remarks>
/// <para>
/// This method does not take an <see cref= "T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/> use one of the <see cref="M:Fatal(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void FatalFormat(string format, object arg1, object arg2, object arg3)
=> LogImpl(
Level.Fatal,
new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),
null);
/// <summary>
/// Logs a formatted message string with the <c>Fatal</c> level.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref = "T:System.Exception"/> object to include in the log event.
/// To pass an<see cref="T:System.Exception"/> use one of the <see cref="M:Fatal(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void FatalFormat(string format, params object[] args)
=> LogImpl(
Level.Fatal,
new SystemStringFormat(CultureInfo.InvariantCulture, format, args),
null);
/// <summary>
/// Logs a formatted message string with the <c>Fatal</c> level.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider" /> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
/// <remarks>
/// <para>
/// The message is formatted using the <c>String.Format</c> method. See <see cref="M:String.Format(string, object[])"/>
/// for details of the syntax of the format string and the behavior of the formatting.
/// </para>
/// <para>
/// This method does not take an <see cref="T:System.Exception"/> object to include in the log event.
/// To pass an <see cref="T:System.Exception"/>, use one of the <see cref="M:Fatal(object,Exception)"/> methods instead.
/// </para>
/// </remarks>
[DebuggerStepThrough]
public void FatalFormat(IFormatProvider provider, string format, params object[] args)
=> LogImpl(
Level.Fatal,
new SystemStringFormat(provider, format, args),
null);
#endregion
private static string PrefixScopesIfAny(string message)
{
var scopeMsg = Scope.ScopeMessage;
return scopeMsg is null ? message : string.Concat(scopeMsg, " ", message);
}
private bool IsEnabledFor(Level level) => _logger.Logger.IsEnabledFor(level);
private void LogImpl(Level level, object message, Exception exception)
{
if (!IsEnabledFor(level)) { return; }
_logger.Logger.Log(
ThisDeclaringType,
level,
PrefixScopesIfAny(message is string msgStr ? msgStr : message.ToString()),
exception);
}
}
}
================================================
FILE: Easy.Logger/Log4NetService.cs
================================================
namespace Easy.Logger
{
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Easy.Logger.Interfaces;
using log4net;
using log4net.Config;
using log4net.Repository;
/// <summary>
/// An implementation of the <see cref="ILogService"/> based on <c>log4net</c>.
/// </summary>
public sealed class Log4NetService : ILogService
{
private readonly ILoggerRepository _repository;
private static readonly Lazy<Log4NetService> Lazy =
new Lazy<Log4NetService>(() => new Log4NetService(), true);
/// <summary>
/// Returns a single instance of the <see cref="Log4NetService"/>
/// </summary>
public static Log4NetService Instance => Lazy.Value;
/// <summary>
/// Creates and configures an instance of the <see cref="Log4NetService"/> by looking for a
/// default <c>log4net.config</c> file in the executing directory and monitoring it for changes.
/// </summary>
/// <exception cref="FileNotFoundException">
/// Thrown when a valid <c>log4net.config</c> file is not found.
/// </exception>
private Log4NetService()
{
string log4NetConfigDir;
Assembly assembly;
#if NET_STANDARD
log4NetConfigDir = AppContext.BaseDirectory;
assembly = GetType().GetTypeInfo().Assembly;
#else
log4NetConfigDir = AppDomain.CurrentDomain.BaseDirectory;
assembly = GetType().Assembly;
#endif
_repository = LogManager.GetRepository(assembly);
var defaultConfigFile = new FileInfo(Path.Combine(log4NetConfigDir, "log4net.config"));
if (!defaultConfigFile.Exists) { return; }
ConfigureImpl(_repository, defaultConfigFile);
}
/// <summary>
/// Gets the configuration file used to configure the <see cref="Log4NetService"/>.
/// </summary>
public FileInfo Configuration { get; private set; }
/// <summary>
/// Provides an override to configure the <see cref="Log4NetService"/> with a valid <c>log4net</c>
/// config file represented as <paramref name="configFile"/> and monitor any changes to it.
/// </summary>
/// <param name="configFile">Points to a valid log4net config file.</param>
/// <exception cref="ArgumentException">Thrown when <c>log4net</c> config file is null.</exception>
/// <exception cref="FileNotFoundException">Thrown when a valid <c>log4net.config</c> file is not found.</exception>
/// <remarks>
/// If this method is not used, the <see cref="Log4NetService"/> will be configured by looking for a
/// default <c>log4net.config</c> file in the executing directory.
/// </remarks>
public void Configure(FileInfo configFile)
{
if (configFile is null)
{
throw new ArgumentException(nameof(configFile) + " cannot be null", nameof(configFile));
}
if (!configFile.Exists)
{
throw new FileNotFoundException(
"Could not find a valid log4net configuration file",
configFile.FullName);
}
ConfigureImpl(_repository, configFile);
}
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <paramref name="loggerName"/>.
/// </summary>
/// <param name="loggerName">The name for which an <see cref="IEasyLogger"/> should be returned</param>
/// <exception cref="InvalidOperationException">Thrown if <see cref="Log4NetService"/> is not configured with a valid configuration file.</exception>
/// <returns>The <see cref="IEasyLogger"/>An instance of the logger.</returns>
[DebuggerStepThrough]
public IEasyLogger GetLogger(string loggerName)
{
EnsureConfigured();
return new Log4NetLogger(LogManager.GetLogger(_repository.Name, loggerName));
}
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <paramref name="loggerType"/>.
/// </summary>
/// <param name="loggerType">The <see cref="Type"/> for which an <see cref="IEasyLogger"/> should be returned</param>
/// <exception cref="InvalidOperationException">Thrown if <see cref="Log4NetService"/> is not configured with a valid configuration file.</exception>
/// <returns>The <see cref="IEasyLogger"/>An instance of the logger.</returns>
[DebuggerStepThrough]
public IEasyLogger GetLogger(Type loggerType)
{
EnsureConfigured();
return new Log4NetLogger(LogManager.GetLogger(loggerType));
}
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type for which an <see cref="IEasyLogger"/> should be returned</typeparam>
/// <exception cref="InvalidOperationException">Thrown if <see cref="Log4NetService"/> is not configured with a valid configuration file.</exception>
/// <returns>The <see cref="IEasyLogger"/>An instance of the logger.</returns>
[DebuggerStepThrough]
public IEasyLogger GetLogger<T>()
{
EnsureConfigured();
return new Log4NetLogger<T>();
}
/// <summary>
/// Disposes the <see cref="Log4NetService"/>
/// </summary>
[DebuggerStepThrough]
public void Dispose() => LogManager.Shutdown();
private void ConfigureImpl(ILoggerRepository repository, FileInfo configFile)
{
log4net.Util.SystemInfo.NullText = string.Empty;
XmlConfigurator.ConfigureAndWatch(repository, configFile);
Configuration = configFile;
}
private void EnsureConfigured()
{
if (Configuration != null) { return; }
throw new InvalidOperationException(
GetType().Name + " needs to be configured with a valid configuration file.");
}
}
}
================================================
FILE: Easy.Logger/Properties/AssemblyInfo.cs
================================================
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Easy.Logger.Extensions")]
[assembly: InternalsVisibleTo("Easy.Logger.Tests.Unit")]
================================================
FILE: Easy.Logger/Scope.cs
================================================
namespace Easy.Logger
{
using System;
using log4net;
using log4net.Util;
/// <summary>
/// Provides scoped logging.
/// </summary>
public struct Scope : IDisposable
{
private const string ContextName = "Easy.Logger.Scope";
private static readonly LogicalThreadContextStacks Stacks = LogicalThreadContext.Stacks;
/// <summary>
/// Creates an instance of the <see cref="Scope"/> with the given <paramref name="name"/>.
/// </summary>
internal Scope(string name) => Stacks[ContextName].Push(name);
internal static string ScopeMessage => Stacks[ContextName].ToString();
/// <summary>
/// Removes the scope.
/// </summary>
public void Dispose() => Stacks[ContextName].Pop();
/// <summary>
/// Returns the current context information for this scope.
/// </summary>
/// <returns></returns>
public override string ToString() => ScopeMessage;
}
}
================================================
FILE: Easy.Logger/Sequencer.cs
================================================
namespace Easy.Logger
{
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// A single worker implementation of the <c>Producer-Consumer</c> pattern.
/// </summary>
/// <typeparam name="T">The type of the object to be produced/consumed</typeparam>
internal sealed class Sequencer<T>
{
private readonly CancellationTokenSource _cts;
private readonly BlockingCollection<T> _queue;
private readonly Task _worker;
/// <summary>
/// Creates an instance of <see cref="Sequencer{T}"/>
/// </summary>
/// <param name="consumer">The action to be executed when consuming the item.</param>
public Sequencer(Action<T> consumer) : this(-1, consumer) {}
/// <summary>
/// Creates an instance of <see cref="Sequencer{T}"/>
/// </summary>
/// <param name="consumer">The action to be executed when consuming the item.</param>
/// <param name="boundedCapacity">
/// The bounded size of the queue.
/// Any more items added will block until there is more space available.
/// </param>
public Sequencer(Action<T> consumer, int boundedCapacity) : this(boundedCapacity, consumer)
{
if (boundedCapacity <= 0)
{
throw new ArgumentException("Bounded capacity should be greater than zero.");
}
}
private Sequencer(int boundedCapacity, Action<T> consumer)
{
if (consumer == null)
{
throw new ArgumentNullException(nameof(consumer));
}
_queue = boundedCapacity > 0 ? new BlockingCollection<T>(boundedCapacity) : new BlockingCollection<T>();
_cts = new CancellationTokenSource();
_worker = GetConsumer(consumer);
}
/// <summary>
/// Returns the bounded capacity of the underlying queue. -1 for unbounded.
/// </summary>
public int Capacity => _queue.BoundedCapacity;
/// <summary>
/// Returns the count of items that are pending consumption.
/// </summary>
public uint PendingCount => (uint) _queue.Count;
/// <summary>
/// Returns the pending items in the queue. Note, the items are valid as
/// the snapshot at the time of invocation.
/// </summary>
public T[] PendingItems => _queue.ToArray();
/// <summary>
/// Gets whether <see cref="Sequencer{T}"/> has started to shutdown.
/// </summary>
public bool ShutdownRequested { get; private set; }
/// <summary>
/// Thrown when the <see cref="_worker"/> throws an exception.
/// </summary>
public event EventHandler<SequencerExceptionEventArgs> OnException;
/// <summary>
/// Adds the specified item to the <see cref="Sequencer{T}"/>.
/// This method blocks if the queue is full and until there is more room.
/// </summary>
/// <param name="item">The item to be added.</param>
public void Enqueue(T item)
{
try
{
_queue.Add(item);
} catch (Exception e)
{
OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException("Exception occurred when adding item.", e)));
}
}
/// <summary>
/// Attempts to add the specified item to the <see cref="Sequencer{T}"/>.
/// </summary>
/// <param name="item">The item to be added.</param>
/// <returns>
/// <c>True</c> if item could be added; otherwise <c>False</c>.
/// If the item is a duplicate, and the underlying collection does
/// not accept duplicate items, then an InvalidOperationException is thrown.
/// </returns>
public bool TryEnqueue(T item)
{
try
{
return _queue.TryAdd(item);
} catch (Exception e)
{
OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException("Exception occurred when adding item.", e)));
return false;
}
}
/// <summary>
/// Attempts to add the specified item to the <see cref="Sequencer{T}"/>.
/// </summary>
/// <param name="item">The item to be added.</param>
/// <param name="timeout">
/// A <c>TimeSpan</c> that represents the number of milliseconds
/// to wait, or a <c>TimeSpan</c> that represents -1 milliseconds to wait indefinitely.
/// </param>
/// <returns>
/// <c>True</c> if the item could be added to the collection within the specified time span; otherwise, <c>False</c>.
/// </returns>
public bool TryEnqueue(T item, TimeSpan timeout)
{
try
{
return _queue.TryAdd(item, timeout);
} catch (Exception e)
{
OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException("Exception occurred when adding item.", e)));
return false;
}
}
/// <summary>
/// Marks the <see cref="Sequencer{T}"/> instance as not accepting
/// any more items. Any outstanding items will be consumed.
/// </summary>
/// <param name="waitForPendingItems">
/// Flag indicating whether to wait for pending items to be processed.
/// </param>
public void Shutdown(bool waitForPendingItems = true)
{
lock (_queue)
{
_queue.CompleteAdding();
ShutdownRequested = true;
}
if (waitForPendingItems)
{
try { _worker.Wait(); } catch (Exception) { /* ignored */ }
}
_cts.Cancel();
_cts.Dispose();
_queue.Dispose();
}
private Task GetConsumer(Action<T> consumer)
{
var cToken = _cts.Token;
return Task.Factory.StartNew(() =>
{
foreach (var item in _queue.GetConsumingEnumerable(cToken))
{
cToken.ThrowIfCancellationRequested();
try
{
consumer(item);
}
catch (Exception e)
{
OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException("Exception occurred.", e)));
}
}
}, cToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
/// <summary>
/// This class used as a container for when an <see cref="System.Exception"/>
/// is raised by the <see cref="Sequencer{T}"/>
/// </summary>
internal sealed class SequencerExceptionEventArgs : EventArgs
{
/// <summary>
/// Creates an instance of the <see cref="Sequencer{T}"/>
/// </summary>
/// <param name="e">The <see cref="System.Exception"/></param>
public SequencerExceptionEventArgs(SequencerException e) => Exception = e;
/// <summary>
/// The <see cref="System.Exception"/> raised by the <see cref="Sequencer{T}"/>.
/// </summary>
public SequencerException Exception { get; }
}
/// <summary>
/// The <see cref="System.Exception"/> thrown by the <see cref="Sequencer{T}"/>.
/// </summary>
internal sealed class SequencerException : Exception
{
/// <summary>
/// Creates an instance of the <see cref="SequencerException"/>.
/// </summary>
public SequencerException() { }
/// <summary>
/// Creates an instance of the <see cref="SequencerException"/>.
/// </summary>
/// <param name="message">The message for the <see cref="Exception"/></param>
public SequencerException(string message) : base(message) { }
/// <summary>
/// Creates an instance of the <see cref="SequencerException"/>.
/// </summary>
/// <param name="message">The message for the <see cref="Exception"/></param>
/// <param name="innerException">The inner exception</param>
public SequencerException(string message, Exception innerException) : base(message, innerException) { }
}
}
================================================
FILE: Easy.Logger/sample-log4net.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<root>
<!--
1.OFF - nothing gets logged
2.FATAL
3.ERROR
4.WARN
5.INFO
6.DEBUG
7.ALL - everything gets logged
Max no. of appenders = 4
-->
<level value="ALL"/>
<appender-ref ref="AsyncBufferingForwarder"/>
<!-- <appender-ref ref="BufferingForwarder" />-->
</root>
<appender name="AsyncBufferingForwarder" type="Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger">
<lossy value="false" />
<bufferSize value="512" />
<!-- Value in milliseconds -->
<idleTime value="500" />
<!-- Must fix Message, ThreadName and Exception -->
<fix value="268" />
<!-- Alternate syntax:
<fix value="Message, ThreadName, Exception" />
-->
<!--Any other appender or forwarder...-->
<appender-ref ref="RollingFileAppender"/>
<appender-ref ref="HTTPAppender" />
</appender>
<!-- http://stackoverflow.com/a/11351400/1226568 -->
<appender name="BufferingForwarder" type="log4net.Appender.BufferingForwardingAppender">
<bufferSize value="512" />
<lossy value="false" />
<fix value="268" />
<!-- Alternate syntax:
<fix value="Message, ThreadName, Exception" />
-->
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="WARN"/>
</evaluator>
<appender-ref ref="RollingFileAppender-1" />
</appender>
<!-- Preferred -->
<appender name="RollingFileAppender-1" type="log4net.Appender.RollingFileAppender">
<file value="Logs\" />
<appendToFile value="false"/>
<rollingStyle value="Composite"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="50MB"/>
<staticLogFileName value="false"/>
<datePattern value="'App-'yyyy-MM-dd'.log'"/>
<preserveLogFileNameExtension value="true"/>
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
<!-- Also works -->
<appender name="RollingFileAppender-2" type="log4net.Appender.RollingFileAppender">
<file value="Logs\App.log" />
<appendToFile value="false"/>
<rollingStyle value="Composite"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="50MB"/>
<staticLogFileName value="false"/>
<datePattern value="yyyy-MM-dd"/>
<preserveLogFileNameExtension value="true"/>
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
<appender name="RollingPerExecutionFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="App-%date{yyyy-MM-dd}.log" />
<appendToFile value="false"/>
<rollingStyle value="Composite"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="50MB"/>
<staticLogFileName value="true"/>
<preserveLogFileNameExtension value="true"/>
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
<appender name="HTTPAppender" type="Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions">
<name value="Integration.Tests" />
<endpoint value="http://localhost:1234" />
<includeHost value="true" />
</appender>
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="FATAL"/>
<foreColor value="Red"/>
<backColor value="White"/>
</mapping>
<mapping>
<level value="ERROR"/>
<foreColor value="Red, HighIntensity" />
</mapping>
<mapping>
<level value="WARN"/>
<foreColor value="Yellow"/>
</mapping>
<mapping>
<level value="INFO"/>
<foreColor value="Cyan"/>
</mapping>
<mapping>
<level value="DEBUG"/>
<foreColor value="Green"/>
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
</log4net>
================================================
FILE: Easy.Logger.Benchmarker/App.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<gcServer enabled="true"/>
</runtime>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
</configuration>
================================================
FILE: Easy.Logger.Benchmarker/Easy.Logger.Benchmarker.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2B08D00F-F8E8-418E-9533-A50E92E9448F}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Easy.Logger.Benchmarker</RootNamespace>
<AssemblyName>Easy.Logger.Benchmarker</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="the-log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Easy.Logger.Extensions\Easy.Logger.Extensions.csproj">
<Project>{84135fc0-fe53-47ac-ab47-fc5b0cdb2ecd}</Project>
<Name>Easy.Logger.Extensions</Name>
</ProjectReference>
<ProjectReference Include="..\Easy.Logger.Interfaces\Easy.Logger.Interfaces.csproj">
<Project>{80248AFD-52ED-47A0-B077-97AE8DC067CF}</Project>
<Name>Easy.Logger.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\Easy.Logger\Easy.Logger.csproj">
<Project>{f1cfacd6-414e-4921-90fd-0949764e1756}</Project>
<Name>Easy.Logger</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
================================================
FILE: Easy.Logger.Benchmarker/Program.cs
================================================
namespace Easy.Logger.Benchmarker
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Easy.Logger.Interfaces;
internal class Program
{
private readonly TimeSpan _duration;
private readonly IEasyLogger _logger;
private static void Main(string[] args)
{
var duration = TimeSpan.FromSeconds(10);
if (args.Length > 0)
{
var seconds = int.Parse(args[0]);
duration = TimeSpan.FromSeconds(seconds);
}
new Program(duration).Run();
Console.WriteLine("[{0:HH:mm:ss.fff}] - [Program] - Disposing", DateTimeOffset.UtcNow);
Log4NetService.Instance.Dispose();
Console.WriteLine("[{0:HH:mm:ss.fff}] - [Program] - Disposed", DateTimeOffset.UtcNow);
Console.WriteLine("Gen 0: {0}", GC.CollectionCount(0).ToString());
Console.WriteLine("Gen 1: {0}", GC.CollectionCount(1).ToString());
Console.WriteLine("Gen 2: {0}", GC.CollectionCount(2).ToString());
}
public Program(TimeSpan duration)
{
var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "the-log4net.config"));
Log4NetService.Instance.Configure(configFile);
_duration = duration;
_logger = Log4NetService.Instance.GetLogger<Program>();
}
private void Run()
{
_logger.Info("Benchmarking starting");
TestThroughput();
// TestMultiThreading();
// TestIdle();
_logger.Warn("Benchmarking ended");
}
private void TestThroughput()
{
Console.WriteLine("------------Testing Throughput------------");
var sw = Stopwatch.StartNew();
long counter = 0;
while (sw.Elapsed < _duration)
{
counter++;
_logger.DebugFormat("Counter is: {0}", counter.ToString());
}
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", counter, sw.Elapsed.ToString());
}
private void TestMultiThreading()
{
Console.WriteLine("------------Testing MultiThreading------------");
const int WorkerCount = 6;
long totalCounter = 0;
Func<int> action = () =>
{
var localCounter = 0;
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _duration)
{
_logger.DebugFormat("Counter is: {0}", (++localCounter).ToString());
}
return localCounter;
};
var totalSw = Stopwatch.StartNew();
Parallel.For(
0,
WorkerCount,
new ParallelOptions { MaxDegreeOfParallelism = WorkerCount },
() => 0,
(i, state, partial) => action(),
partialCounter => Interlocked.Add(ref totalCounter, partialCounter));
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", totalCounter, totalSw.Elapsed.ToString());
}
private void TestIdle()
{
Console.WriteLine("------------Testing Idle------------");
var sw = Stopwatch.StartNew();
long counter = 0;
while (sw.Elapsed < _duration)
{
counter++;
_logger.DebugFormat("Counter is: {0}", counter.ToString());
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", counter, sw.Elapsed.ToString());
}
}
}
================================================
FILE: Easy.Logger.Benchmarker/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Easy.Logger.Benchmarker")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Easy.Logger.Benchmarker")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2b08d00f-f8e8-418e-9533-a50e92e9448f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: Easy.Logger.Benchmarker/the-log4net.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<root>
<level value="ALL"/>
<appender-ref ref="AsyncBufferingForwarder"/>
</root>
<appender name="AsyncBufferingForwarder" type="Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger">
<lossy value="false" />
<bufferSize value="512" />
<idleTime value="200" />
<fix value="Message, ThreadName, Exception" />
<appender-ref ref="RollingFile"/>
<!-- <appender-ref ref="HTTPAppender"/> -->
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="Logs\" />
<appendToFile value="false"/>
<rollingStyle value="Composite"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="50MB"/>
<staticLogFileName value="false"/>
<datePattern value="'Benchmarker-'yyyy-MM-dd'.log'"/>
<preserveLogFileNameExtension value="true"/>
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
<appender name="HTTPAppender" type="Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions">
<name value="Integration.Tests" />
<endpoint value="http://localhost:5001" />
<includeHost value="true" />
</appender>
</log4net>
================================================
FILE: Easy.Logger.Benchmarker.Core/Easy.Logger.Benchmarker.Core.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Easy.Logger\Easy.Logger.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="App.config">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
================================================
FILE: Easy.Logger.Benchmarker.Core/Program.cs
================================================
namespace Easy.Logger.Benchmarker.Core
{
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Easy.Logger.Interfaces;
internal class Program
{
private readonly TimeSpan _duration;
private readonly IEasyLogger _logger;
private static void Main(string[] args)
{
var duration = TimeSpan.FromSeconds(10);
if (args.Length > 0)
{
var seconds = int.Parse(args[0]);
duration = TimeSpan.FromSeconds(seconds);
}
new Program(duration).Run();
Log4NetService.Instance.Dispose();
}
public Program(TimeSpan duration)
{
_duration = duration;
_logger = Log4NetService.Instance.GetLogger<Program>();
}
private void Run()
{
_logger.Info("Benchmarking starting");
TestThroughput();
// TestMultiThreading();
// TestIdle();
_logger.Warn("Benchmarking ended");
Console.WriteLine("Gen 0: {0}", GC.CollectionCount(0).ToString());
Console.WriteLine("Gen 1: {0}", GC.CollectionCount(1).ToString());
Console.WriteLine("Gen 2: {0}", GC.CollectionCount(2).ToString());
}
private void TestThroughput()
{
Console.WriteLine("------------Testing Throughput------------");
var sw = Stopwatch.StartNew();
long counter = 0;
while (sw.Elapsed < _duration)
{
counter++;
_logger.DebugFormat("Counter is: {0}", counter.ToString());
}
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", counter, sw.Elapsed.ToString());
}
private void TestMultiThreading()
{
Console.WriteLine("------------Testing MultiThreading------------");
const int WorkerCount = 6;
long totalCounter = 0;
Func<int> action = () =>
{
var localCounter = 0;
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _duration)
{
_logger.DebugFormat("Counter is: {0}", (++localCounter).ToString());
}
return localCounter;
};
var totalSw = Stopwatch.StartNew();
Parallel.For(
0,
WorkerCount,
new ParallelOptions { MaxDegreeOfParallelism = WorkerCount },
() => 0,
(i, state, partial) => action(),
partialCounter => Interlocked.Add(ref totalCounter, partialCounter));
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", totalCounter, totalSw.Elapsed.ToString());
}
private void TestIdle()
{
Console.WriteLine("------------Testing Idle------------");
var sw = Stopwatch.StartNew();
long counter = 0;
while (sw.Elapsed < _duration)
{
counter++;
_logger.DebugFormat("Counter is: {0}", counter.ToString());
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("Counter reached: {0:n0}, Time Taken: {1}", counter, sw.Elapsed.ToString());
}
}
}
================================================
FILE: Easy.Logger.Benchmarker.Core/log4net.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<root>
<level value="ALL"/>
<appender-ref ref="AsyncBufferingForwarder"/>
</root>
<appender name="AsyncBufferingForwarder" type="Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger">
<lossy value="false" />
<bufferSize value="512" />
<idleTime value="200" />
<fix value="Message, ThreadName, Exception" />
<appender-ref ref="RollingFile"/>
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="Benchmarker.Core-%date{yyyy-MM-dd}.log" />
<appendToFile value="false"/>
<rollingStyle value="Composite"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="50MB"/>
<staticLogFileName value="true"/>
<datePattern value="yyyy-MM-dd"/>
<preserveLogFileNameExtension value="true"/>
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
</layout>
</appender>
</log4net>
================================================
FILE: Easy.Logger.Extensions/Easy.Logger.Extensions.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Easy.Logger.Extensions</PackageId>
<Description>Additional features and functionalities to extend Easy.Logger</Description>
<Authors>Nima Ara</Authors>
<Copyright>2024 Nima Ara</Copyright>
<PackageTags>log4net;Logging;Easy;Logger;Log;Extensions</PackageTags>
<PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>-</PackageReleaseNotes>
<RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net48;netstandard1.3;netstandard2.0</TargetFrameworks>
<AssemblyTitle>Easy Logger Extensions</AssemblyTitle>
<AssemblyName>Easy.Logger.Extensions</AssemblyName>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<DefineConstants>NETSTANDARD1_3</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="log4net" Version="2.0.15" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.3' ">
<PackageReference Include="Utf8Json" Version="1.3.7" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
================================================
FILE: Easy.Logger.Extensions/HTTPAppender.cs
================================================
namespace Easy.Logger.Extensions
{
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using log4net.Appender;
using log4net.Core;
#if NETSTANDARD1_3
using System.Text;
using Newtonsoft.Json;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
using Newtonsoft.Json.Serialization;
#else
using Utf8Json;
using Utf8Json.Formatters;
using Utf8Json.Resolvers;
#endif
/// <summary>
/// An appender for <c>POSTing</c> log events to a given endpoint.
/// </summary>
public sealed class HTTPAppender : AppenderSkeleton
{
private static readonly ThreadLocal<LoggingEvent[]> _singleLogEventPool;
#if NETSTANDARD1_3
private static readonly JsonSerializer Serializer;
#endif
private readonly int _pid;
private readonly string _processName;
private int _counter;
private string _host, _sender;
static HTTPAppender()
{
_singleLogEventPool = new ThreadLocal<LoggingEvent[]>(() => new LoggingEvent[1]);
#if NETSTANDARD1_3
Serializer = new JsonSerializer
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
Formatting = Formatting.None
};
#endif
}
/// <summary>
/// Creates an instance of the <see cref="HTTPAppender"/>.
/// </summary>
public HTTPAppender()
{
using (var p = Process.GetCurrentProcess())
{
_pid = p.Id;
_processName = p.ProcessName;
}
}
/// <summary>
/// Gets the endpoint to which log events are <c>POSTed</c> to.
/// </summary>
public string Endpoint { get; set; }
/// <summary>
/// Gets the flag indicating whether the host name should be included in the payload.
/// </summary>
public bool IncludeHost { get; set; }
/// <summary>
/// Activates the appender options.
/// </summary>
public override void ActivateOptions()
{
base.ActivateOptions();
if (string.IsNullOrWhiteSpace(Name))
{
Name = Guid.NewGuid().ToString();
}
if (string.IsNullOrWhiteSpace(Endpoint)
|| !Endpoint.StartsWith("HTTP", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid endpoint.");
}
_host = IncludeHost ? Dns.GetHostName() : null;
_sender = Name;
}
/// <summary>
/// Serializes and <c>POST</c>s the given <paramref name="logEvent"/> to <see cref="Endpoint"/>.
/// </summary>
protected override void Append(LoggingEvent logEvent)
{
var pool = _singleLogEventPool.Value;
pool[0] = logEvent;
Append(pool);
}
/// <summary>
/// Serializes and <c>POST</c>s the given <paramref name="logEvents"/> to <see cref="Endpoint"/>.
/// </summary>
protected override void Append(LoggingEvent[] logEvents) => Post(logEvents);
private async void Post(LoggingEvent[] logEvents)
{
var payload = new Payload
{
PID = _pid,
ProcessName = _processName,
Host = _host,
Sender = _sender,
TimestampUTC = DateTimeOffset.UtcNow,
BatchNo = Interlocked.Increment(ref _counter),
Entries = GetEntries(logEvents)
};
if (!await DoPost(payload).ConfigureAwait(false))
{
// Try once more
await DoPost(payload).ConfigureAwait(false);
}
}
private async Task<bool> DoPost(Payload payload)
{
var req = (HttpWebRequest)WebRequest.Create(Endpoint);
/* ToDo - Consider setting the proxy
* string proxyUrl = "proxy.myproxy.com";
* int proxyPort = 8080;
* WebProxy myProxy = new WebProxy(proxyUrl, proxyPort);
*
* HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(Endpoint);
* req.Proxy = myProxy;
*/
req.Proxy = null;
req.Method = "POST";
req.ContentType = "application/json";
try
{
using (var stream = await req.GetRequestStreamAsync().ConfigureAwait(false))
{
#if NETSTANDARD1_3
using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
using (JsonTextWriter jsonWriter = new JsonTextWriter(writer))
{
Serializer.Serialize(jsonWriter, payload);
}
#else
JsonSerializer.Serialize(stream, payload, StandardResolver.AllowPrivateCamelCase);
#endif
using (var resp = (HttpWebResponse) await req.GetResponseAsync().ConfigureAwait(false))
{
return IsSuccessStatusCode(resp);
}
}
} catch (WebException)
{
return false;
}
}
private static Entry[] GetEntries(LoggingEvent[] logEvents)
{
var result = new Entry[logEvents.Length];
for (var i = 0; i < logEvents.Length; i++)
{
var curr = logEvents[i];
result[i] = new Entry
{
DateTimeOffset = new DateTimeOffset(curr.TimeStamp),
LoggerName = curr.LoggerName,
Level = curr.Level?.DisplayName,
ThreadID = curr.ThreadName,
Message = curr.RenderedMessage,
Exception = curr.ExceptionObject
};
}
return result;
}
private static bool IsSuccessStatusCode(HttpWebResponse response)
{
if (response.StatusCode >= HttpStatusCode.OK)
{
return response.StatusCode <= (HttpStatusCode)299;
}
return false;
}
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
private sealed class Payload
{
// ReSharper disable once InconsistentNaming
public int PID { get; set; }
public string ProcessName { get; set; }
public string Host { get; set; }
public string Sender { get; set; }
public DateTimeOffset TimestampUTC { get; set; }
public int BatchNo { get; set; }
public Entry[] Entries { get; set; }
}
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
private struct Entry
{
public DateTimeOffset DateTimeOffset { get; set; }
public string LoggerName { get; set; }
public string Level { get; set; }
// ReSharper disable once InconsistentNaming
public string ThreadID { get; set; }
public string Message { get; set; }
public Exception Exception { get; set; }
}
}
}
================================================
FILE: Easy.Logger.Extensions/Properties/AssemblyInfo.cs
================================================
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Easy.Logger.Tests.Unit")]
================================================
FILE: Easy.Logger.Extensions.Microsoft/Easy.Logger.Extensions.Microsoft.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Easy.Logger.Extensions.Microsoft</PackageId>
<Description>Easy.Logger implementation of the Microsoft.Extensions.Logging package which can also be used in ASP.NET Core.</Description>
<Authors>Nima Ara</Authors>
<Copyright>2024 Nima Ara</Copyright>
<PackageTags>log4net;Logging;Easy;Logger;Log;Microsoft.Extensions.Logging</PackageTags>
<PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>-</PackageReleaseNotes>
<RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyTitle>Easy Logger ASP.NET Core</AssemblyTitle>
<AssemblyName>Easy.Logger.Extensions.Microsoft</AssemblyName>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Easy.Logger" Version="4.0.0" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
================================================
FILE: Easy.Logger.Extensions.Microsoft/EasyLogger.cs
================================================
namespace Easy.Logger.Extensions.Microsoft
{
using System;
using Easy.Logger.Interfaces;
using global::Microsoft.Extensions.Logging;
/// <summary>
/// An implementation of the <see cref="ILogger"/> based on
/// <see href="https://github.com/NimaAra/Easy.Logger"/>.
/// </summary>
public sealed class EasyLogger : ILogger
{
private readonly IEasyLogger _logger;
/// <summary>
/// Creates an instance of the <see cref="EasyLogger"/>.
/// </summary>
// ReSharper disable once UnusedMember.Global
public EasyLogger() { }
/// <summary>
/// Creates an instance of the <see cref="EasyLogger"/>.
/// </summary>
/// <param name="easyLogger">An instance of the <see cref="IEasyLogger"/>.</param>
public EasyLogger(IEasyLogger easyLogger) => _logger = easyLogger;
/// <summary>
/// Gets the flag indicating whether the given <paramref name="logLevel"/> is enabled.
/// </summary>
public bool IsEnabled(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
return _logger.IsTraceEnabled;
case LogLevel.Debug:
return _logger.IsDebugEnabled;
case LogLevel.Information:
return _logger.IsInfoEnabled;
case LogLevel.Warning:
return _logger.IsWarnEnabled;
case LogLevel.Error:
return _logger.IsErrorEnabled;
case LogLevel.Critical:
return _logger.IsFatalEnabled;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);
}
}
/// <summary>
/// Returns an <see cref="T:System.IDisposable" /> which allows the caller to specify a scope as
/// <paramref name="state" /> which will then be rendered as part of the message.
/// </summary>
/// <param name="state">The scope identifier.</param>
public IDisposable BeginScope<TState>(TState state) => _logger.GetScopedLogger(state.ToString());
/// <summary>
/// Does the logging.
/// </summary>
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel)) { return; }
if (formatter is null) { throw new ArgumentNullException(nameof(formatter)); }
var message = formatter(state, exception);
if (message is null) { return; }
switch (logLevel)
{
case LogLevel.Trace:
_logger.Trace(message, exception);
break;
case LogLevel.Debug:
_logger.Debug(message, exception);
break;
case LogLevel.Information:
_logger.Info(message, exception);
break;
case LogLevel.Warning:
_logger.Warn(message, exception);
break;
case LogLevel.Error:
_logger.Error(message, exception);
break;
case LogLevel.Critical:
_logger.Fatal(message, exception);
break;
default:
_logger.WarnFormat("Invalid {0}: {1} | {2}", nameof(logLevel), logLevel, message);
break;
}
}
}
}
================================================
FILE: Easy.Logger.Extensions.Microsoft/EasyLoggerConfig.cs
================================================
namespace Easy.Logger.Extensions.Microsoft
{
using System.IO;
/// <summary>
/// An abstraction for configuring <see cref="EasyLoggerProvider"/>.
/// </summary>
public sealed class EasyLoggerConfig : IEasyLoggerConfig
{
/// <summary>
/// Gets the configuration file.
/// </summary>
public FileInfo ConfigFile { get; }
/// <summary>
/// Creates an instance of the <see cref="EasyLoggerConfig"/>.
/// </summary>
/// <param name="file">The configuration file.</param>
public EasyLoggerConfig(FileInfo file) => ConfigFile = file;
}
}
================================================
FILE: Easy.Logger.Extensions.Microsoft/EasyLoggerProvider.cs
================================================
namespace Easy.Logger.Extensions.Microsoft
{
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using global::Microsoft.Extensions.Logging;
/// <summary>
/// An implementation of the <see cref="ILoggerProvider"/>.
/// </summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class EasyLoggerProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, EasyLogger> _loggerCache =
new ConcurrentDictionary<string, EasyLogger>();
/// <summary>
/// Creates an instance of the <see cref="EasyLoggerProvider"/>.
/// <remarks>A valid <c>log4net</c> configuration file must be present in the working directory.</remarks>
/// </summary>
public EasyLoggerProvider() { }
/// <summary>
/// Creates an instance of the <see cref="EasyLoggerProvider"/>.
/// </summary>
/// <param name="config">A valid <c>log4net</c> configuration file.</param>
public EasyLoggerProvider(IEasyLoggerConfig config) =>
Log4NetService.Instance.Configure(config.ConfigFile);
/// <summary>
/// Creates an <see cref="ILogger"/> with the given <paramref name="categoryName"/>.
/// </summary>
public ILogger CreateLogger(string categoryName) =>
_loggerCache.GetOrAdd(categoryName, x => new EasyLogger(Log4NetService.Instance.GetLogger(x)));
/// <summary>
/// Disposes the instance flushing any pending log entries.
/// </summary>
public void Dispose() => Log4NetService.Instance.Dispose();
}
}
================================================
FILE: Easy.Logger.Extensions.Microsoft/IEasyLoggerConfig.cs
================================================
namespace Easy.Logger.Extensions.Microsoft
{
using System.IO;
/// <summary>
/// Specifies the contract for an implementation of <see cref="IEasyLoggerConfig"/>.
/// </summary>
public interface IEasyLoggerConfig
{
/// <summary>
/// Gets the configuration file.
/// </summary>
FileInfo ConfigFile { get; }
}
}
================================================
FILE: Easy.Logger.Extensions.Microsoft/LoggingBuilderExtensions.cs
================================================
namespace Easy.Logger.Extensions.Microsoft
{
using System.IO;
using global::Microsoft.Extensions.DependencyInjection;
using global::Microsoft.Extensions.Logging;
/// <summary>
/// A set of methods to extend the behavior of <see cref="ILoggingBuilder"/>.
/// </summary>
public static class LoggingBuilderExtensions
{
/// <summary>
/// Adds <see cref="EasyLoggerProvider"/> to <paramref name="builder"/>.
/// </summary>
public static void AddEasyLogger(this ILoggingBuilder builder) =>
builder.Services.AddSingleton<ILoggerProvider, EasyLoggerProvider>();
/// <summary>
/// Adds <see cref="EasyLoggerProvider"/> to <paramref name="builder"/>.
/// </summary>
/// <param name="builder">An instance of the <see cref="ILoggingBuilder"/>.</param>
/// <param name="log4netConfig">A valid configuration file.</param>
public static void AddEasyLogger(this ILoggingBuilder builder, FileInfo log4netConfig)
{
builder.Services.AddSingleton<IEasyLoggerConfig>(new EasyLoggerConfig(log4netConfig));
builder.AddEasyLogger();
}
}
}
================================================
FILE: Easy.Logger.Extensions.Microsoft/README.md
================================================
### Setting up in ASP.NET Core:
The current implementation is based on [log4net](https://logging.apache.org/log4net/).
```csharp
internal class Program
{
private static void Main()
{
using (var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseUrls("http://*:8081")
.ConfigureLogging(Startup.ConfigureLogging)
.Configure(Startup.Configure)
.Build())
{
host.Run();
}
}
}
internal static class Startup
{
public static void ConfigureLogging(WebHostBuilderContext ctx, ILoggingBuilder builder)
{
builder.AddConfiguration(ctx.Configuration.GetSection("Logging"));
if (ctx.HostingEnvironment.IsDevelopment())
{
builder.AddConsole();
}
// Use the default log
builder.AddEasyLogger();
/*
var configFile = new FileInfo("sample-log4net.config");
builder.AddEasyLogger(configFile);
*/
}
public static void Configure(IApplicationBuilder app) { ... }
}
```
================================================
FILE: Easy.Logger.Interfaces/Easy.Logger.Interfaces.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Easy.Logger.Interfaces</PackageId>
<Description>Contains a set of interfaces to be implemented by any logger.</Description>
<Authors>Nima Ara</Authors>
<Copyright>2024 Nima Ara</Copyright>
<PackageTags>log4net;Logging;Easy;Logger;Log;Interfaces</PackageTags>
<PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>-</PackageReleaseNotes>
<RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard1.3;net48</TargetFrameworks>
<AssemblyTitle>Easy Logger Interfaces</AssemblyTitle>
<AssemblyName>Easy.Logger.Interfaces</AssemblyName>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
================================================
FILE: Easy.Logger.Interfaces/IEasyLogger.cs
================================================
namespace Easy.Logger.Interfaces
{
using System;
/// <summary>
/// The <see cref="IEasyLogger"/> interface specifying
/// available methods for different levels of logging.
/// </summary>
public interface IEasyLogger
{
/// <summary>
/// Gets the logger name.
/// </summary>
string Name { get; }
/// <summary>
/// Returns an <see cref="IDisposable"/> which allows the caller to specify a scope as
/// <paramref name="name"/> which will then be rendered as part of the message.
/// </summary>
/// <param name="name">The name of the scope</param>
IDisposable GetScopedLogger(string name);
#region Levels Enabled
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Trace</c> messages.
/// </summary>
bool IsTraceEnabled { get; }
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <see cref="System.Diagnostics.Debug"/> messages.
/// </summary>
bool IsDebugEnabled { get; }
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Info</c> messages.
/// </summary>
bool IsInfoEnabled { get; }
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Warn</c> messages.
/// </summary>
bool IsWarnEnabled { get; }
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Error</c> messages.
/// </summary>
bool IsErrorEnabled { get; }
/// <summary>
/// Gets the flag indicating whether the logger is enabled for
/// <c>Fatal</c> messages.
/// </summary>
bool IsFatalEnabled { get; }
#endregion
#region Trace
/// <summary>
/// Logs a <c>Trace</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Trace(object message);
/// <summary>
/// Logs a <c>Trace</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Trace(object message, Exception exception);
/// <summary>
/// Logs a <c>Trace</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void TraceFormat(string format, object arg);
/// <summary>
/// Logs a <c>Trace</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void TraceFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Trace</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void TraceFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Trace</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void TraceFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Trace</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void TraceFormat(IFormatProvider provider, string format, params object[] args);
#endregion
#region Debug
/// <summary>
/// Logs a <c>Debug</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Debug(object message);
/// <summary>
/// Logs a <c>Debug</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Debug(object message, Exception exception);
/// <summary>
/// Logs a <c>Debug</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void DebugFormat(string format, object arg);
/// <summary>
/// Logs a <c>Debug</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void DebugFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Debug</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void DebugFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Debug</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void DebugFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Debug</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void DebugFormat(IFormatProvider provider, string format, params object[] args);
#endregion
#region Info
/// <summary>
/// Logs a <c>Info</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Info(object message);
/// <summary>
/// Logs a <c>Info</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Info(object message, Exception exception);
/// <summary>
/// Logs a <c>Info</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void InfoFormat(string format, object arg);
/// <summary>
/// Logs a <c>Info</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void InfoFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Info</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void InfoFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Info</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void InfoFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Info</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void InfoFormat(IFormatProvider provider, string format, params object[] args);
#endregion
#region Warn
/// <summary>
/// Logs a <c>Warn</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Warn(object message);
/// <summary>
/// Logs a <c>Warn</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Warn(object message, Exception exception);
/// <summary>
/// Logs a <c>Warn</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void WarnFormat(string format, object arg);
/// <summary>
/// Logs a <c>Warn</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void WarnFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Warn</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void WarnFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Warn</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void WarnFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Warn</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void WarnFormat(IFormatProvider provider, string format, params object[] args);
#endregion
#region Error
/// <summary>
/// Logs a <c>Error</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Error(object message);
/// <summary>
/// Logs a <c>Error</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Error(object message, Exception exception);
/// <summary>
/// Logs a <c>Error</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void ErrorFormat(string format, object arg);
/// <summary>
/// Logs a <c>Error</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void ErrorFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Error</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void ErrorFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Error</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void ErrorFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Error</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void ErrorFormat(IFormatProvider provider, string format, params object[] args);
#endregion
#region Fatal
/// <summary>
/// Logs a <c>Fatal</c> level message object.
/// </summary>
/// <param name="message">The message object to be logged.</param>
void Fatal(object message);
/// <summary>
/// Logs a <c>Fatal</c> level message object including the stack trace of
/// the <see cref="T:System.Exception"/> passed as a parameter.
/// </summary>
/// <param name="message">The message object to be logged.</param>
/// <param name="exception">The exception to be logged, including its stack trace.</param>
void Fatal(object message, Exception exception);
/// <summary>
/// Logs a <c>Fatal</c> level formatted message string with the given <paramref name="arg"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg">The object to format</param>
void FatalFormat(string format, object arg);
/// <summary>
/// Logs a <c>Fatal</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
void FatalFormat(string format, object arg1, object arg2);
/// <summary>
/// Logs a <c>Fatal</c> level formatted message string with the given arguments.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="arg1">The first object to format</param>
/// <param name="arg2">The second object to format</param>
/// <param name="arg3">The third object to format</param>
void FatalFormat(string format, object arg1, object arg2, object arg3);
/// <summary>
/// Logs a <c>Fatal</c> level formatted message string with the given <paramref name="args"/>.
/// </summary>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void FatalFormat(string format, params object[] args);
/// <summary>
/// Logs a <c>Fatal</c> level formatted message string with the
/// given <paramref name="args"/> and a given <paramref name="provider"/>.
/// </summary>
/// <param name="provider">An <see cref= "T:System.IFormatProvider"/> that supplies culture-specific formatting information</param>
/// <param name="format">A String containing zero or more format items</param>
/// <param name="args">An Object array containing zero or more objects to format</param>
void FatalFormat(IFormatProvider provider, string format, params object[] args);
#endregion
}
/// <summary>
/// The <see cref="IEasyLogger{T}"/> interface specifying
/// available methods for different levels of logging.
/// </summary>
// ReSharper disable once UnusedTypeParameter
public interface IEasyLogger<T> : IEasyLogger { }
}
================================================
FILE: Easy.Logger.Interfaces/ILogService.cs
================================================
namespace Easy.Logger.Interfaces
{
using System;
using System.IO;
/// <summary>
/// The <see cref="ILogService"/> specifying the methods relating
/// to configuring and obtaining an instance of <see cref="IEasyLogger"/>.
/// </summary>
public interface ILogService : IDisposable
{
/// <summary>
/// Gets the configuration file used to configure the <see cref="ILogService"/>.
/// </summary>
FileInfo Configuration { get; }
/// <summary>
/// Configures the logging service by using the specified configuration file.
/// </summary>
void Configure(FileInfo configFile);
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <paramref name="loggerName"/>.
/// </summary>
/// <param name="loggerName">The name for which an <see cref="IEasyLogger"/> should be returned</param>
/// <returns>The <see cref="IEasyLogger"/></returns>
IEasyLogger GetLogger(string loggerName);
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <paramref name="loggerType"/>.
/// </summary>
/// <param name="loggerType">The <see cref="Type"/> for which an <see cref="IEasyLogger"/> should be returned</param>
/// <returns>The <see cref="IEasyLogger"/></returns>
IEasyLogger GetLogger(Type loggerType);
/// <summary>
/// Obtains an <see cref="IEasyLogger"/> for the given <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type for which an <see cref="IEasyLogger"/> should be returned</typeparam>
/// <returns>The <see cref="IEasyLogger"/></returns>
IEasyLogger GetLogger<T>();
}
}
================================================
FILE: Easy.Logger.Tests.Integration/Context.cs
================================================
namespace Easy.Logger.Tests.Integration
{
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Easy.Logger.Interfaces;
using Easy.Logger.Tests.Integration.Models;
using NUnit.Framework;
using Shouldly;
[TestFixture]
internal class Context
{
private ILogService _logService;
private FileInfo _configFile, _logFile;
private EasyLogListener _listener;
private ConcurrentQueue<LogPayload> _receivedPayloads;
[SetUp]
public void Setup()
{
_logService = Log4NetService.Instance;
_configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "integration.tests-log4net.config"));
_logFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "Integration.Tests.log"));
if (_logFile.Exists) { _logFile.Delete(); }
_logService.Configure(_configFile);
_receivedPayloads = new ConcurrentQueue<LogPayload>();
StartHttpServer();
}
[Test]
public void Run()
{
IEasyLogger logger = _logService.GetLogger(GetType());
logger.Debug("Something is about to happen...");
// Should not be replaced by Task.Delay as
// Thread name will be changed after await
// which will fail the log content assertions
Thread.Sleep(TimeSpan.FromSeconds(1.5));
logger.InfoFormat("What's your number? It's: {0}", 1234.ToString());
logger.Error("Ooops I did it again!", new ArgumentNullException("cooCoo", "Parameter cannot be null."));
logger.FatalFormat("Going home now - {0}", new ApplicationException("CiaoCiao"));
Thread.Sleep(TimeSpan.FromSeconds(1.5));
using (logger.GetScopedLogger("[Scope 1]"))
{
logger.Debug("This should be inside scope 1.");
using (logger.GetScopedLogger("[Scope 2]"))
{
logger.WarnFormat("This should be inside scope 1 and scope 2. {0}", "Bar");
}
logger.Error("This should still be inside scope 1.");
}
logger.Fatal("This should not be inside any scope.");
Thread.Sleep(TimeSpan.FromSeconds(1.5));
IEasyLogger anotherLogger = _logService.GetLogger<Context>();
anotherLogger.ShouldNotBeNull();
anotherLogger.Name.ShouldBe("Easy.Logger.Tests.Integration.Context");
}
[TearDown]
public void TestTearDown()
{
_logService.Dispose();
_listener.Dispose();
CheckLogFileContent(_logFile);
CheckReceivedPayloads(_receivedPayloads.ToArray());
}
private static void CheckLogFileContent(FileSystemInfo logFile)
{
Regex dateTimeRegex = new(@"^\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\,\d{3}\]\s\[", RegexOptions.Compiled);
string[] lines = File.ReadAllLines(logFile.FullName);
lines.Length.ShouldBe(9);
foreach (string line in lines.Where(x => x != "System.ArgumentNullException: Parameter cannot be null. (Parameter 'cooCoo')"))
{
dateTimeRegex.IsMatch(line).ShouldBeTrue();
}
lines[0].ShouldEndWith(" [DEBUG] [NonParallelWorker] [Context] Something is about to happen...");
lines[1].ShouldEndWith(" [INFO ] [NonParallelWorker] [Context] What's your number? It's: 1234");
lines[2].ShouldEndWith(" [ERROR] [NonParallelWorker] [Context] Ooops I did it again!");
lines[3].ShouldBe("System.ArgumentNullException: Parameter cannot be null. (Parameter 'cooCoo')");
lines[4].ShouldEndWith(" [FATAL] [NonParallelWorker] [Context] Going home now - System.ApplicationException: CiaoCiao");
lines[5].ShouldEndWith(" [DEBUG] [NonParallelWorker] [Context] [Scope 1] This should be inside scope 1.");
lines[6].ShouldEndWith(" [WARN ] [NonParallelWorker] [Context] [Scope 1] [Scope 2] This should be inside scope 1 and scope 2. Bar");
lines[7].ShouldEndWith(" [ERROR] [NonParallelWorker] [Context] [Scope 1] This should still be inside scope 1.");
lines[8].ShouldEndWith(" [FATAL] [NonParallelWorker] [Context] This should not be inside any scope.");
}
private static void CheckReceivedPayloads(LogPayload[] payloads)
{
int pid;
string processName;
using (Process p = Process.GetCurrentProcess())
{
pid = p.Id;
processName = p.ProcessName;
}
payloads.Length.ShouldBe(3);
payloads[0].BatchNo.ShouldBe(1);
payloads[0].PID.ShouldBe(pid);
payloads[0].ProcessName.ShouldBe(processName);
payloads[0].Host.ShouldBe(Dns.GetHostName());
payloads[0].TimestampUTC.ShouldBeOfType<DateTimeOffset>();
payloads[0].TimestampUTC.ShouldNotBe(default(DateTimeOffset));
payloads[0].Sender.ShouldBe("Integration.Tests");
payloads[0].Entries.Length.ShouldBe(1);
payloads[1].BatchNo.ShouldBe(2);
payloads[1].PID.ShouldBe(pid);
payloads[1].ProcessName.ShouldBe(processName);
payloads[1].Host.ShouldBe(Dns.GetHostName());
payloads[1].TimestampUTC.ShouldBeOfType<DateTimeOffset>();
payloads[1].TimestampUTC.ShouldNotBe(default(DateTimeOffset));
payloads[1].Sender.ShouldBe("Integration.Tests");
payloads[1].Entries.Length.ShouldBe(3);
payloads[2].BatchNo.ShouldBe(3);
payloads[2].PID.ShouldBe(pid);
payloads[2].ProcessName.ShouldBe(processName);
payloads[2].Host.ShouldBe(Dns.GetHostName());
payloads[2].TimestampUTC.ShouldBeOfType<DateTimeOffset>();
payloads[2].TimestampUTC.ShouldNotBe(default(DateTimeOffset));
payloads[2].Sender.ShouldBe("Integration.Tests");
payloads[2].Entries.Length.ShouldBe(4);
LogEntry[] allEntries = payloads.SelectMany(p => p.Entries).ToArray();
allEntries[0].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[0].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[0].Level.ShouldBe("DEBUG");
allEntries[0].ThreadID.ShouldBe("NonParallelWorker");
allEntries[0].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[0].Message.ShouldBe("Something is about to happen...");
allEntries[0].Exception.ShouldBeNull();
allEntries[1].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[1].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[1].Level.ShouldBe("INFO");
allEntries[1].ThreadID.ShouldBe("NonParallelWorker");
allEntries[1].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[1].Message.ShouldBe("What's your number? It's: 1234");
allEntries[1].Exception.ShouldBeNull();
allEntries[2].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[2].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[2].Level.ShouldBe("ERROR");
allEntries[2].ThreadID.ShouldBe("NonParallelWorker");
allEntries[2].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[2].Message.ShouldBe("Ooops I did it again!");
allEntries[2].Exception.ShouldNotBeNull();
allEntries[2].Exception.Count.ShouldBe(6);
allEntries[2].Exception["className"].ShouldBe("System.ArgumentNullException");
allEntries[2].Exception["paramName"].ShouldBe("cooCoo");
allEntries[2].Exception["stackTrace"].ShouldBeNull();
allEntries[2].Exception["innerException"].ShouldBeNull();
allEntries[2].Exception["message"].ShouldBe("Parameter cannot be null. (Parameter 'cooCoo')");
allEntries[2].Exception["source"].ShouldBeNull();
allEntries[3].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[3].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[3].Level.ShouldBe("FATAL");
allEntries[3].ThreadID.ShouldBe("NonParallelWorker");
allEntries[3].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[3].Message.ShouldBe("Going home now - System.ApplicationException: CiaoCiao");
allEntries[3].Exception.ShouldBeNull();
allEntries[4].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[4].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[4].Level.ShouldBe("DEBUG");
allEntries[4].ThreadID.ShouldBe("NonParallelWorker");
allEntries[4].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[4].Message.ShouldBe("[Scope 1] This should be inside scope 1.");
allEntries[4].Exception.ShouldBeNull();
allEntries[5].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[5].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[5].Level.ShouldBe("WARN");
allEntries[5].ThreadID.ShouldBe("NonParallelWorker");
allEntries[5].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[5].Message.ShouldBe("[Scope 1] [Scope 2] This should be inside scope 1 and scope 2. Bar");
allEntries[5].Exception.ShouldBeNull();
allEntries[6].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[6].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[6].Level.ShouldBe("ERROR");
allEntries[6].ThreadID.ShouldBe("NonParallelWorker");
allEntries[6].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[6].Message.ShouldBe("[Scope 1] This should still be inside scope 1.");
allEntries[6].Exception.ShouldBeNull();
allEntries[7].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();
allEntries[7].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));
allEntries[7].Level.ShouldBe("FATAL");
allEntries[7].ThreadID.ShouldBe("NonParallelWorker");
allEntries[7].LoggerName.ShouldBe("Easy.Logger.Tests.Integration.Context");
allEntries[7].Message.ShouldBe("This should not be inside any scope.");
allEntries[7].Exception.ShouldBeNull();
Array.ForEach(payloads, Console.Write);
}
private void StartHttpServer()
{
string endpointStr = XDocument.Load(_configFile.FullName)
.Descendants()
.Elements()
.Single(e => e.Attributes().Any(a => a.Name == "name" && a.Value == "HTTPAppender"))
.Elements("endpoint")
.Single()
.Attribute("value")?.Value;
_listener = new EasyLogListener(new Uri(endpointStr ?? throw new InvalidOperationException()));
_listener.OnError += (sender, exception) => throw exception;
_listener.OnPayload += (sender, payload) => _receivedPayloads.Enqueue(payload);
_listener.Start();
}
}
}
================================================
FILE: Easy.Logger.Tests.Integration/Easy.Logger.Tests.Integration.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Easy.Logger.Extensions\Easy.Logger.Extensions.csproj" />
<ProjectReference Include="..\Easy.Logger\Easy.Logger.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="integration.tests-log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
================================================
FILE: Easy.Logger.Tests.Integration/EasyLogListener.cs
================================================
namespace Easy.Logger.Tests.Integration
{
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using Easy.Logger.Tests.Integration.Models;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
/// <summary>
/// A simple <c>HTTP</c> listener for receiving <see cref="LogPayload"/>s.
/// </summary>
public sealed class EasyLogListener : IDisposable
{
private const int StatusCodeAccepted = 202;
private const int StatusCodeBadRequest = 400;
private const int StatusCodeNotAcceptable = 406;
private static readonly byte[] InvalidRequestMethodMessage = Encoding.UTF8.GetBytes("You can only POST a valid JSON payload to this server.");
private static readonly byte[] InvalidPayloadMessage = Encoding.UTF8.GetBytes("Invalid payload. Payload should be a valid JSON.");
private static readonly Regex JSONMediaTypeRegex =
new Regex(@"([aA][pP][pP][lL][iI][cC][aA][tT][iI][oO][nN]/[jJ][sS][oO][nN]|[tT][eE][xX][tT]/[jJ][sS][oO][nN]|[aA][pP][pP][lL][iI][cC][aA][tT][iI][oO][nN]/[vV][nN][dD]\.\S*\+[jJ][sS][oO][nN])", RegexOptions.Compiled);
private readonly HttpListener _listener;
private readonly JsonSerializer _serializer;
private int _isDisposing;
/// <summary>
/// Invoked when a new <see cref="LogPayload"/> is <c>POST</c>ed.
/// </summary>
public event EventHandler<LogPayload> OnPayload;
/// <summary>
/// Invoked when there is an error during the deserialization of <see cref="LogPayload"/>.
/// </summary>
public event EventHandler<Exception> OnError;
/// <summary>
/// Creates an instance of <see cref="EasyLogListener"/>.
/// </summary>
/// <param name="prefix">The prefix the listener will be listening on.</param>
public EasyLogListener(Uri prefix)
{
_listener = new HttpListener();
_listener.Prefixes.Add(prefix.AbsoluteUri);
_serializer = new JsonSerializer
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
Formatting = Newtonsoft.Json.Formatting.None
};
}
/// <summary>
/// Starts listening for payloads.
/// </summary>
public void Start() => StartListening();
/// <summary>
/// Stops and releases resources used by the instance.
/// </summary>
public void Dispose()
{
Interlocked.Increment(ref _isDisposing);
_listener.Stop();
_listener.Close();
}
private async void StartListening()
{
_listener.Start();
while (_listener.IsListening)
{
HttpListenerContext ctx = null;
try
{
ctx = await _listener.GetContextAsync().ConfigureAwait(false);
}
catch (HttpListenerException e) { HandleException(e); }
catch (ObjectDisposedException) { /* Shutting down */ }
if (ctx == null) { return; }
var req = ctx.Request;
var resp = ctx.Response;
if (IsValidRequest(req))
{
try
{
var payload = Deserialize(req.InputStream);
OnPayload?.Invoke(this, payload);
resp.StatusCode = StatusCodeAccepted;
}
catch (HttpListenerException) { continue; }
catch (Exception e)
{
HandleException(e);
resp.StatusCode = StatusCodeBadRequest;
await resp.OutputStream.WriteAsync(
InvalidPayloadMessage, 0, InvalidPayloadMessage.Length);
}
} else
{
resp.StatusCode = StatusCodeNotAcceptable;
await resp.OutputStream.WriteAsync(
InvalidRequestMethodMessage, 0, InvalidRequestMethodMessage.Length);
}
resp.OutputStream.Close();
}
}
private LogPayload Deserialize(Stream stream)
{
using (var sr = new StreamReader(stream, Encoding.UTF8, true, 1024, true))
using (var jsonTextReader = new JsonTextReader(sr) { CloseInput = false })
{
return _serializer.Deserialize<LogPayload>(jsonTextReader);
}
}
private void HandleException(Exception e)
{
// If we are NOT disposing, then we should report exception
if (Interlocked.CompareExchange(ref _isDisposing, 0, 0) == 0)
{
OnError?.Invoke(this, e);
}
}
private static bool IsValidRequest(HttpListenerRequest req)
{
return req.HttpMethod.Equals("POST", StringComparison.Ordinal)
&& IsJSONMediaType(req.ContentType);
}
private static bool IsJSONMediaType(string contentType) =>
!string.IsNullOrEmpty(contentType) && JSONMediaTypeRegex.IsMatch(contentType);
}
}
================================================
FILE: Easy.Logger.Tests.Integration/Models/LogPayload.cs
================================================
// ReSharper disable InconsistentNaming
namespace Easy.Logger.Tests.Integration.Models
{
using System;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// Represents a log payload.
/// </summary>
public sealed class LogPayload
{
/// <summary>
/// Gets or sets the host.
/// </summary>
public string Host { get; set; }
/// <summary>
/// Gets or sets the process id.
/// </summary>
// ReSharper disable once InconsistentNaming
public int PID { get; set; }
/// <summary>
/// Gets or sets the process name.
/// </summary>
public string ProcessName { get; set; }
/// <summary>
/// Gets or sets the sender name.
/// </summary>
public string Sender { get; set; }
/// <summary>
/// Gets or sets the time stamp at which the payload was generated.
/// </summary>
public DateTimeOffset TimestampUTC { get; set; }
/// <summary>
/// Gets or sets the batch number to which the payload belong.
/// </summary>
public int BatchNo { get; set; }
/// <summary>
/// Gets or sets the entries contained in the payload.
/// </summary>
public LogEntry[] Entries { get; set; }
/// <summary>
/// Returns a textual representation of the payload.
/// </summary>
/// <returns></returns>
public override string ToString()
{
var builder = new StringBuilder();
foreach (var e in Entries)
{
builder.AppendFormat("[{0:yyyy-MM-dd HH:mm:ss,fff}] [{1}] [{2}] [{3}] [{4}] [B:{5,5}] [{6,-5}] [{7,-2}] [{8}] - {9}",
e.DateTimeOffset,
Host,
Sender,
PID,
ProcessName,
BatchNo,
e.Level,
e.ThreadID,
e.LoggerName,
e.Message);
builder.AppendLine();
if (e.Exception != null)
{
builder.Append("\t").Append(e.Exception).AppendLine();
}
}
return builder.ToString();
}
}
/// <summary>
/// Represents a log entry.
/// </summary>
public class LogEntry
{
/// <summary>
/// Gets or sets the date and time the entry was generated.
/// </summary>
public DateTimeOffset DateTimeOffset { get; set; }
/// <summary>
/// Gets or sets the logger name.
/// </summary>
public string LoggerName { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
public string Level { get; set; }
/// <summary>
/// Gets or sets the thread ID.
/// </summary>
// ReSharper disable once InconsistentNaming
public string ThreadID { get; set; }
/// <summary>
/// Gets or sets the message.
/// </summary>
public string Message { get; set; }
/// <summary>
/// Gets or sets the exception if any.
/// </summary>
public Dictionary<string, object> Exception { get; set; }
}
}
================================================
FILE: Easy.Logger.Tests.Integration/integration.tests-log4net.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<root>
<level value="ALL"/>
<appender-ref ref="AsyncBufferingForwarder"/>
</root>
<appender name="AsyncBufferingForwarder" type="Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger">
<lossy value="false" />
<bufferSize value="512" />
<idleTime value="200" />
<fix value="Message, ThreadName, Exception" />
<appender-ref ref="FileAppender"/>
<appender-ref ref="HTTPAppender"/>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="Logs\Integration.Tests.log" />
<appendToFile value="false"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] %message%newline%exception"/>
</layout>
</appender>
<appender name="HTTPAppender" type="Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions">
<name value="Integration.Tests" />
<endpoint value="http://localhost:1234" />
<includeHost value="true" />
</appender>
</log4net>
================================================
FILE: Easy.Logger.Tests.Unit/AsyncBufferingForwardingAppenderTests.cs
================================================
namespace Easy.Logger.Tests.Unit
{
using System.Collections.Generic;
using System.Threading.Tasks;
using log4net.Appender;
using log4net.Core;
using Moq;
using NUnit.Framework;
using Shouldly;
[TestFixture]
public sealed class AsyncBufferingForwardingAppenderTests
{
[Test]
public async Task When_testing_a_non_lossy_forwarder()
{
var forwarder = new AsyncBufferingForwardingAppender();
forwarder.BufferSize.ShouldBe(512);
forwarder.Lossy.ShouldBeFalse();
forwarder.Name.ShouldBeNull();
forwarder.Appenders.Count.ShouldBe(0);
forwarder.Threshold.ShouldBeNull();
var loggedEvents = new List<LoggingEvent>();
var mockedAppender = new Mock<IAppender>();
mockedAppender
.Setup(s => s.DoAppend(It.IsAny<LoggingEvent>()))
.Callback<LoggingEvent>(loggingEvent => loggedEvents.Add(loggingEvent));
forwarder.AddAppender(mockedAppender.Object);
forwarder.ActivateOptions();
loggedEvents.Count.ShouldBe(0);
forwarder.DoAppend(new LoggingEvent(new LoggingEventData()));
loggedEvents.Count.ShouldBe(0);
await Task.Delay(1000);
loggedEvents.Count.ShouldBe(1);
forwarder.DoAppend(new[]
{
new LoggingEvent(new LoggingEventData()),
new LoggingEvent(new LoggingEventData())
});
await Task.Delay(1000);
loggedEvents.Count.ShouldBe(3);
forwarder.DoAppend(new LoggingEvent(new LoggingEventData()));
loggedEvents.Count.ShouldBe(3);
forwarder.Close();
loggedEvents.Count.ShouldBe(4);
}
[Test]
public async Task When_testing_a_lossy_forwarder()
{
var forwarder = new AsyncBufferingForwardingAppender
{
Lossy = true,
LossyEvaluator = new LevelEvaluator(Level.Error),
Fix = FixFlags.ThreadName | FixFlags.Exception | FixFlags.Message
};
forwarder.BufferSize.ShouldBe(512);
forwarder.Lossy.ShouldBeTrue();
forwarder.Name.ShouldBeNull();
forwarder.Appenders.Count.ShouldBe(0);
forwarder.Threshold.ShouldBeNull();
forwarder.LossyEvaluator.ShouldBeOfType<LevelEvaluator>();
var loggedEvents = new List<LoggingEvent>();
var mockedAppender = new Mock<IAppender>();
mockedAppender
.Setup(s => s.DoAppend(It.IsAny<LoggingEvent>()))
.Callback<LoggingEvent>(loggingEvent => loggedEvents.Add(loggingEvent));
forwarder.AddAppender(mockedAppender.Object);
forwarder.ActivateOptions();
await Task.Delay(1000);
loggedEvents.Count.ShouldBe(1);
loggedEvents[0].Level.ShouldBe(Level.Warn);
loggedEvents[0].LoggerName.ShouldBe("AsyncBufferingForwardingAppender");
loggedEvents[0].RenderedMessage
.ShouldContain("This is a 'lossy' appender therefore log messages may be dropped.");
var event1 = new LoggingEvent(new LoggingEventData { Level = Level.Info });
var event2 = new LoggingEvent(new LoggingEventData { Level = Level.Debug });
var event3 = new LoggingEvent(new LoggingEventData { Level = Level.Error });
var event4 = new LoggingEvent(new LoggingEventData { Level = Level.Warn });
var event5 = new LoggingEvent(new LoggingEventData { Level = Level.Fatal });
forwarder.LossyEvaluator.IsTriggeringEvent(event1).ShouldBeFalse();
forwarder.LossyEvaluator.IsTriggeringEvent(event2).ShouldBeFalse();
forwarder.LossyEvaluator.IsTriggeringEvent(event3).ShouldBeTrue();
forwarder.LossyEvaluator.IsTriggeringEvent(event4).ShouldBeFalse();
forwarder.LossyEvaluator.IsTriggeringEvent(event5).ShouldBeTrue();
forwarder.DoAppend(event1);
await Task.Delay(1000);
loggedEvents.Count.ShouldBe(1);
loggedEvents[0].Level.ShouldBe(Level.Warn);
forwarder.DoAppend(new[] { event2, event3, event4 });
forwarder.Flush(true);
await Task.Delay(1000);
loggedEvents.Count.ShouldBe(2);
loggedEvents[0].Level.ShouldBe(Level.Warn);
loggedEvents[1].Level.ShouldBe(Level.Error);
forwarder.DoAppend(event5);
loggedEvents.Count.ShouldBe(2);
loggedEvents[0].Level.ShouldBe(Level.Warn);
loggedEvents[1].Level.ShouldBe(Level.Error);
forwarder.Close();
loggedEvents.Count.ShouldBe(3);
loggedEvents[0].Level.ShouldBe(Level.Warn);
loggedEvents[1].Level.ShouldBe(Level.Error);
loggedEvents[2].Level.ShouldBe(Level.Fatal);
}
}
}
================================================
FILE: Easy.Logger.Tests.Unit/CreatingNewLog4NetServiceTests.cs
================================================
namespace Easy.Logger.Tests.Unit
{
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;
using Helpers;
using NUnit.Framework;
using Shouldly;
[TestFixture]
public sealed class CreatingNewLog4NetServiceTests
{
private const string SampleAppName = "Easy.Logger.Tests.SampleLoggerApp.exe";
private const string LogfileName = "--LOGFILE--.log";
[Test]
public async Task When_creating_a_log4net_service_with_default_configuration()
{
DirectoryInfo extractedApp = null;
try
{
extractedApp = ExtractSampleApp();
var pathToSampleApp = Path.Combine(extractedApp.FullName, SampleAppName);
var log4NetConfigFile = new FileInfo(Path.Combine(extractedApp.FullName, "log4net.config"));
log4NetConfigFile.Exists.ShouldBeFalse();
File.WriteAllText(log4NetConfigFile.FullName, GetLog4NetConfiguration(), Encoding.UTF8);
log4NetConfigFile.Refresh();
log4NetConfigFile.Exists.ShouldBeTrue();
var logFile = new FileInfo(Path.Combine(extractedApp.FullName, LogfileName));
logFile.Exists.ShouldBeFalse();
var errorMessages = new List<string>();
using (var process = ProcessHelper.GetProcess(pathToSampleApp, errorMessages))
{
process.WaitForExit();
process.ExitCode.ShouldBe(0);
logFile.Refresh();
logFile.Exists.ShouldBeTrue();
var entries = File.ReadAllLines(logFile.FullName);
entries.Length.ShouldBe(2);
entries[0].ShouldContain(" [INFO ] [ 1] Program - I am logging....");
entries[1].ShouldContain(" [WARN ] [ 1] Program - I am done logging!");
}
await Task.Delay(TimeSpan.FromSeconds(2));
} finally
{
extractedApp?.Delete(true);
}
}
[Test]
public async Task When_creating_a_log4net_service_with_default_configuration_and_then_confuguring_it_again()
{
var baseDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
var defaultLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, LogfileName));
defaultLogFile.Delete();
// Let's start by default config
var log4NetConfigFile = new FileInfo(Path.Combine(baseDirectory.FullName, "log4net.config"));
log4NetConfigFile.Delete();
File.WriteAllText(log4NetConfigFile.FullName, GetLog4NetConfiguration(), Encoding.UTF8);
log4NetConfigFile.Refresh();
log4NetConfigFile.Exists.ShouldBeTrue();
var logService = Log4NetService.Instance;
var logger = logService.GetLogger("Test");
defaultLogFile.Refresh();
defaultLogFile.Exists.ShouldBeTrue();
logger.Debug("I am in the default log file.");
// Let's create a new log4net config file
const string NewLogFileName = "NewLogFile.log";
var newLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, NewLogFileName));
newLogFile.Delete();
var newConfigContent = GetLog4NetConfiguration().Replace(LogfileName, NewLogFileName);
var newConfigFile = new FileInfo(Path.Combine(baseDirectory.FullName, "log4net.config"));
File.WriteAllText(newConfigFile.FullName, newConfigContent, Encoding.UTF8);
logService.Configure(newConfigFile);
newLogFile.Refresh();
newLogFile.Exists.ShouldBeTrue();
logger = logService.GetLogger(GetType());
logger.Debug("I am in the new log file.");
// Now let's test updating an existing config file
const string NewerLogFileName = "NewerLogFile.log";
var newerLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, NewerLogFileName));
newerLogFile.Delete();
var updatedConfigContent = GetLog4NetConfiguration().Replace(LogfileName, NewerLogFileName);
File.WriteAllText(newConfigFile.FullName, updatedConfigContent, Encoding.UTF8);
logService.Configure(newConfigFile);
logger = logService.GetLogger<CreatingNewLog4NetServiceTests>();
logger.Debug("I am in the newer log file.");
newerLogFile.Refresh();
newerLogFile.Exists.ShouldBeTrue();
logService.Dispose();
var defaultLogFileContent = File.ReadAllText(defaultLogFile.FullName);
var newLogFileContent = File.ReadAllText(newLogFile.FullName);
var newerLogFileContent = File.ReadAllText(newerLogFile.FullName);
defaultLogFileContent.ShouldContain("Test - I am in the default log file.");
newLogFileContent.ShouldContain("CreatingNewLog4NetServiceTests - I am in the new log file.");
newerLogFileContent.ShouldContain("CreatingNewLog4NetServiceTests - I am in the newer log file.");
await Task.Delay(TimeSpan.FromSeconds(2));
log4NetConfigFile.Delete();
newConfigFile.Delete();
defaultLogFile.Delete();
newLogFile.Delete();
newerLogFile.Delete();
}
private static DirectoryInfo ExtractSampleApp()
{
var pathToArchive = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SampleLoggerApp.zip");
var tmpDir = Path.GetTempPath();
var randomDir = Path.GetRandomFileName();
var extractDir = new DirectoryInfo(Path.Combine(tmpDir, randomDir));
Console.WriteLine($"Extracting to: {extractDir.FullName}");
ZipFile.ExtractToDirectory(pathToArchive, extractDir.FullName);
return extractDir;
}
private static string GetLog4NetConfiguration()
{
return @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<log4net>
<root>
<level value=""ALL""/>
<appender-ref ref=""Default""/>
</root>
<appender name=""Default"" type=""Easy.Logger.AsyncBufferingForwardingAppender"">
<appender-ref ref=""RollingFile""/>
</appender>
<appender name=""RollingFile"" type=""log4net.Appender.RollingFileAppender"">
<file type=""log4net.Util.PatternString"" value=""--LOGFILE--.log"" />
<appendToFile value=""false""/>
<rollingStyle value=""Composite""/>
<maxSizeRollBackups value=""-1""/>
<maximumFileSize value=""50MB""/>
<staticLogFileName value=""true""/>
<datePattern value=""yyyy-MM-dd""/>
<layout type=""log4net.Layout.PatternLayout"">
<conversionPattern value=""%date{ISO8601} [%-5level] [%2thread] %logger{1} - %message%newline%exception""/>
</layout>
</appender>
</log4net>";
}
}
}
================================================
FILE: Easy.Logger.Tests.Unit/Easy.Logger.Tests.Unit.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Castle.Core" Version="5.1.1" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Easy.Logger.Extensions\Easy.Logger.Extensions.csproj" />
<ProjectReference Include="..\Easy.Logger\Easy.Logger.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="SampleLoggerApp.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
================================================
FILE: Easy.Logger.Tests.Unit/HTTPAppenderTests.cs
================================================
namespace Easy.Logger.Tests.Unit
{
using System;
using System.Threading.Tasks;
using Easy.Logger.Extensions;
using log4net.Core;
using NUnit.Framework;
using Shouldly;
[TestFixture]
internal sealed class HTTPAppenderTests
{
[Test]
public void When_creating_appender_without_name()
{
var appender = new HTTPAppender
{
Endpoint = "http://localhost:1234"
};
appender.Name.ShouldBeNull();
appender.Endpoint.ShouldBe("http://localhost:1234");
appender.ActivateOptions();
appender.Name.ShouldNotBeNullOrWhiteSpace();
}
[Test]
public void When_activating_appender()
{
var appender = new HTTPAppender
{
Name = "FooBar",
Endpoint = "http://localhost:1234"
};
appender.Name.ShouldBe("FooBar");
appender.Endpoint.ShouldBe("http://localhost:1234");
appender.ActivateOptions();
appender.Name.ShouldBe("FooBar");
}
[Test]
public void When_activating_appender_with_invalid_endpoint()
{
var invalidEndpoints = new[]
{
null,
string.Empty,
" ",
"ftp://localhost:1234",
"tcp://localhost:1234"
};
Array.ForEach(invalidEndpoints, e =>
{
Should.Throw<ArgumentException>(() => new HTTPAppender
{
Endpoint = e
}.ActivateOptions())
.Message.ShouldBe("Invalid endpoint.");
});
}
[Test]
public async Task When_logging_events_through_appender()
{
var appender = new HTTPAppender
{
Name = "Tester",
Endpoint = "http://localhost:1234"
};
appender.DoAppend(new LoggingEvent(new LoggingEventData
{
Message = "Foo",
ExceptionString = "foo",
LoggerName = "boo",
Level = Level.Debug,
TimeStampUtc = DateTime.UtcNow,
ThreadName = "12"
}));
appender.DoAppend(new LoggingEvent(new LoggingEventData { Message = "Bar" }));
appender.DoAppend(new LoggingEvent(new LoggingEventData { Message = "Baz" }));
await Task.Delay(TimeSpan.FromSeconds(1));
appender.Close();
}
}
}
================================================
FILE: Easy.Logger.Tests.Unit/Helpers/ProcessHelper.cs
================================================
namespace Easy.Logger.Tests.Unit.Helpers
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
internal static class ProcessHelper
{
public static Process GetProcess(string processPath, IList<string> outputMessages)
{
var processInfo = new ProcessStartInfo(processPath)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = processInfo
};
var locker = new object();
process.OutputDataReceived += (sender, args) =>
{
if (args?.Data == null) { return; }
lock (locker)
{
outputMessages.Add(args.Data);
}
Debug.WriteLine(args.Data);
};
process.ErrorDataReceived += (sender, args) =>
{
var errMsg = "[Process Error] - ";
if (args?.Data != null)
{
errMsg += args.Data;
} else
{
errMsg += "No data available";
}
lock (locker)
{
outputMessages.Add(errMsg);
}
Console.WriteLine(errMsg);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return process;
}
}
}
================================================
FILE: Easy.Logger.Tests.Unit/Log4NetLoggerTests.cs
================================================
namespace Easy.Logger.Tests.Unit
{
using System;
using System.Globalization;
using Easy.Logger.Interfaces;
using log4net;
using log4net.Core;
using Moq;
using NUnit.Framework;
using Shouldly;
[TestFixture]
public sealed class Log4NetLoggerTests
{
private Mock<ILog> _mockedLogger;
private Mock<ILogger> _mockedInnerLogger;
private IEasyLogger _logger;
[OneTimeSetUp]
public void SetUp()
{
_mockedInnerLogger = new Mock<ILogger>();
_mockedInnerLogger.Setup(l => l.Name).Returns("Inner Logger");
_mockedLogger = new Mock<ILog>();
_mockedLogger.Setup(l => l.Logger).Returns(_mockedInnerLogger.Object);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Trace)).Returns(true);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Debug)).Returns(true);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Info)).Returns(true);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Warn)).Returns(true);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Error)).Returns(true);
_mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Fatal)).Returns(true);
_logger = new Log4NetLogger(_mockedLogger.Object);
_logger.Name.ShouldBe("Inner Logger");
}
[Test]
public void When_logging_using_trace()
{
_logger.Trace("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Trace("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "Ooops", ex), Times.Once);
_logger.TraceFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "E:1", null), Times.Once);
_logger.TraceFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "E:1, 2", null), Times.Once);
_logger.TraceFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "E:1, 2, 3", null), Times.Once);
_logger.TraceFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "E:1, 2, 3, 4, 5", null), Times.Once);
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.TraceFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, "Date: 28/12/2000 01:04:43", null), Times.Once);
}
[Test]
public void When_logging_using_debug()
{
_logger.Debug("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Debug("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, "Ooops", ex), Times.Once);
_logger.DebugFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(2));
_logger.DebugFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(3));
_logger.DebugFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(4));
_logger.DebugFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(5));
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.DebugFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(6));
}
[Test]
public void When_logging_using_info()
{
_logger.Info("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Info("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, "Ooops", ex), Times.Once);
_logger.InfoFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(2));
_logger.InfoFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(3));
_logger.InfoFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(4));
_logger.InfoFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(5));
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.InfoFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(6));
}
[Test]
public void When_logging_using_warn()
{
_logger.Warn("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Warn("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, "Ooops", ex), Times.Once);
_logger.WarnFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(2));
_logger.WarnFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(3));
_logger.WarnFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(4));
_logger.WarnFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(5));
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.WarnFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(6));
}
[Test]
public void When_logging_using_error()
{
_logger.Error("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Error("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, "Ooops", ex), Times.Once);
_logger.ErrorFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(2));
_logger.ErrorFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(3));
_logger.ErrorFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(4));
_logger.ErrorFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(5));
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.ErrorFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(6));
}
[Test]
public void When_logging_using_fatal()
{
_logger.Fatal("Hi");
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, "Hi", null), Times.Once);
var ex = new InvalidOperationException();
_logger.Fatal("Ooops", ex);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, "Ooops", ex), Times.Once);
_logger.FatalFormat("E:{0}", 1);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(2));
_logger.FatalFormat("E:{0}, {1}", 1, 2);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(3));
_logger.FatalFormat("E:{0}, {1}, {2}", 1, 2, 3);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(4));
_logger.FatalFormat("E:{0}, {1}, {2}, {3}, {4}", 1, 2, 3, 4, 5);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(5));
var italianCulture = new CultureInfo("it-It");
var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);
_logger.FatalFormat(italianCulture, "Date: {0}", date);
_mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(6));
}
[Test]
public void When_checking_logger_levels()
{
_logger.IsDebugEnabled.ShouldBeFalse();
_mockedLogger.Verify(l => l.IsDebugEnabled, Times.Once);
_logger.IsInfoEnabled.ShouldBeFalse();
_mockedLogger.Verify(l => l.IsInfoEnabled, Times.Once);
_logger.IsWarnEnabled.ShouldBeFalse();
_mockedLogger.Verify(l => l.IsWarnEnabled, Times.Once);
_logger.IsErrorEnabled.ShouldBeFalse();
_mockedLogger.Verify(l => l.IsErrorEnabled, Times.Once);
_logger.IsFatalEnabled.ShouldBeFalse();
_mockedLogger.Verify(l => l.IsFatalEnabled, Times.Once);
}
}
}
================================================
FILE: Easy.Logger.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Logger", "Easy.Logger\Easy.Logger.csproj", "{F1CFACD6-414E-4921-90FD-0949764E1756}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easy.Logger.Benchmarker", "Easy.Logger.Benchmarker\Easy.Logger.Benchmarker.csproj", "{2B08D00F-F8E8-418E-9533-A50E92E9448F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Logger.Benchmarker.Core", "Easy.Logger.Benchmarker.Core\Easy.Logger.Benchmarker.Core.csproj", "{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easy.Logger.Tests.Unit", "Easy.Logger.Tests.Unit\Easy.Logger.Tests.Unit.csproj", "{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Logger.Interfaces", "Easy.Logger.Interfaces\Easy.Logger.Interfaces.csproj", "{80248AFD-52ED-47A0-B077-97AE8DC067CF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BF77A4B4-7A20-4D72-8E5D-4CA911B5773C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Logger.Extensions", "Easy.Logger.Extensions\Easy.Logger.Extensions.csproj", "{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Easy.Logger.Tests.Integration", "Easy.Logger.Tests.Integration\Easy.Logger.Tests.Integration.csproj", "{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarkers", "Benchmarkers", "{14D6F543-A644-4A00-A740-9DE5EDE5CA30}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CAC5E6B4-BDDA-4373-A2F4-EAE0B825182C}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Easy.Logger.Extensions.Microsoft", "Easy.Logger.Extensions.Microsoft\Easy.Logger.Extensions.Microsoft.csproj", "{DAAFA936-DA6B-4BF5-8681-107923EACFE9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F1CFACD6-414E-4921-90FD-0949764E1756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1CFACD6-414E-4921-90FD-0949764E1756}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1CFACD6-414E-4921-90FD-0949764E1756}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1CFACD6-414E-4921-90FD-0949764E1756}.Release|Any CPU.Build.0 = Release|Any CPU
{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Release|Any CPU.Build.0 = Release|Any CPU
{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Release|Any CPU.Build.0 = Release|Any CPU
{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Release|Any CPU.Build.0 = Release|Any CPU
{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Release|Any CPU.Build.0 = Release|Any CPU
{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Release|Any CPU.
gitextract_ar9s1khg/ ├── .gitattributes ├── .gitignore ├── Easy.Logger/ │ ├── AsyncBufferingForwardingAppender.cs │ ├── Easy.Logger.csproj │ ├── Log4NetLogger.cs │ ├── Log4NetService.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── Scope.cs │ ├── Sequencer.cs │ └── sample-log4net.config ├── Easy.Logger.Benchmarker/ │ ├── App.config │ ├── Easy.Logger.Benchmarker.csproj │ ├── Program.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ └── the-log4net.config ├── Easy.Logger.Benchmarker.Core/ │ ├── Easy.Logger.Benchmarker.Core.csproj │ ├── Program.cs │ └── log4net.config ├── Easy.Logger.Extensions/ │ ├── Easy.Logger.Extensions.csproj │ ├── HTTPAppender.cs │ └── Properties/ │ └── AssemblyInfo.cs ├── Easy.Logger.Extensions.Microsoft/ │ ├── Easy.Logger.Extensions.Microsoft.csproj │ ├── EasyLogger.cs │ ├── EasyLoggerConfig.cs │ ├── EasyLoggerProvider.cs │ ├── IEasyLoggerConfig.cs │ ├── LoggingBuilderExtensions.cs │ └── README.md ├── Easy.Logger.Interfaces/ │ ├── Easy.Logger.Interfaces.csproj │ ├── IEasyLogger.cs │ └── ILogService.cs ├── Easy.Logger.Tests.Integration/ │ ├── Context.cs │ ├── Easy.Logger.Tests.Integration.csproj │ ├── EasyLogListener.cs │ ├── Models/ │ │ └── LogPayload.cs │ └── integration.tests-log4net.config ├── Easy.Logger.Tests.Unit/ │ ├── AsyncBufferingForwardingAppenderTests.cs │ ├── CreatingNewLog4NetServiceTests.cs │ ├── Easy.Logger.Tests.Unit.csproj │ ├── HTTPAppenderTests.cs │ ├── Helpers/ │ │ └── ProcessHelper.cs │ └── Log4NetLoggerTests.cs ├── Easy.Logger.sln ├── LICENSE ├── NuGet-Pack-Easy.Logger.Extensions.Microsoft.bat ├── NuGet-Pack-Easy.Logger.Extensions.bat ├── NuGet-Pack-Easy.Logger.Interfaces.bat ├── NuGet-Pack-Easy.Logger.bat └── README.md
SYMBOL INDEX (223 symbols across 23 files)
FILE: Easy.Logger.Benchmarker.Core/Program.cs
class Program (line 9) | internal class Program
method Main (line 14) | private static void Main(string[] args)
method Program (line 29) | public Program(TimeSpan duration)
method Run (line 35) | private void Run()
method TestThroughput (line 48) | private void TestThroughput()
method TestMultiThreading (line 63) | private void TestMultiThreading()
method TestIdle (line 96) | private void TestIdle()
FILE: Easy.Logger.Benchmarker/Program.cs
class Program (line 10) | internal class Program
method Main (line 15) | private static void Main(string[] args)
method Program (line 36) | public Program(TimeSpan duration)
method Run (line 45) | private void Run()
method TestThroughput (line 54) | private void TestThroughput()
method TestMultiThreading (line 69) | private void TestMultiThreading()
method TestIdle (line 102) | private void TestIdle()
FILE: Easy.Logger.Extensions.Microsoft/EasyLogger.cs
class EasyLogger (line 11) | public sealed class EasyLogger : ILogger
method EasyLogger (line 19) | public EasyLogger() { }
method EasyLogger (line 25) | public EasyLogger(IEasyLogger easyLogger) => _logger = easyLogger;
method IsEnabled (line 30) | public bool IsEnabled(LogLevel logLevel)
method BeginScope (line 56) | public IDisposable BeginScope<TState>(TState state) => _logger.GetScop...
method Log (line 61) | public void Log<TState>(
FILE: Easy.Logger.Extensions.Microsoft/EasyLoggerConfig.cs
class EasyLoggerConfig (line 8) | public sealed class EasyLoggerConfig : IEasyLoggerConfig
method EasyLoggerConfig (line 19) | public EasyLoggerConfig(FileInfo file) => ConfigFile = file;
FILE: Easy.Logger.Extensions.Microsoft/EasyLoggerProvider.cs
class EasyLoggerProvider (line 10) | [SuppressMessage("ReSharper", "UnusedMember.Global")]
method EasyLoggerProvider (line 20) | public EasyLoggerProvider() { }
method EasyLoggerProvider (line 26) | public EasyLoggerProvider(IEasyLoggerConfig config) =>
method CreateLogger (line 32) | public ILogger CreateLogger(string categoryName) =>
method Dispose (line 38) | public void Dispose() => Log4NetService.Instance.Dispose();
FILE: Easy.Logger.Extensions.Microsoft/IEasyLoggerConfig.cs
type IEasyLoggerConfig (line 8) | public interface IEasyLoggerConfig
FILE: Easy.Logger.Extensions.Microsoft/LoggingBuilderExtensions.cs
class LoggingBuilderExtensions (line 10) | public static class LoggingBuilderExtensions
method AddEasyLogger (line 15) | public static void AddEasyLogger(this ILoggingBuilder builder) =>
method AddEasyLogger (line 23) | public static void AddEasyLogger(this ILoggingBuilder builder, FileInf...
FILE: Easy.Logger.Extensions/HTTPAppender.cs
class HTTPAppender (line 26) | public sealed class HTTPAppender : AppenderSkeleton
method HTTPAppender (line 39) | static HTTPAppender()
method HTTPAppender (line 57) | public HTTPAppender()
method ActivateOptions (line 79) | public override void ActivateOptions()
method Append (line 101) | protected override void Append(LoggingEvent logEvent)
method Append (line 111) | protected override void Append(LoggingEvent[] logEvents) => Post(logEv...
method Post (line 113) | private async void Post(LoggingEvent[] logEvents)
method DoPost (line 133) | private async Task<bool> DoPost(Payload payload)
method GetEntries (line 173) | private static Entry[] GetEntries(LoggingEvent[] logEvents)
method IsSuccessStatusCode (line 193) | private static bool IsSuccessStatusCode(HttpWebResponse response)
class Payload (line 202) | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
type Entry (line 215) | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
FILE: Easy.Logger.Interfaces/IEasyLogger.cs
type IEasyLogger (line 9) | public interface IEasyLogger
method GetScopedLogger (line 21) | IDisposable GetScopedLogger(string name);
method Trace (line 66) | void Trace(object message);
method Trace (line 74) | void Trace(object message, Exception exception);
method TraceFormat (line 81) | void TraceFormat(string format, object arg);
method TraceFormat (line 89) | void TraceFormat(string format, object arg1, object arg2);
method TraceFormat (line 98) | void TraceFormat(string format, object arg1, object arg2, object arg3);
method TraceFormat (line 105) | void TraceFormat(string format, params object[] args);
method TraceFormat (line 114) | void TraceFormat(IFormatProvider provider, string format, params objec...
method Debug (line 122) | void Debug(object message);
method Debug (line 130) | void Debug(object message, Exception exception);
method DebugFormat (line 137) | void DebugFormat(string format, object arg);
method DebugFormat (line 145) | void DebugFormat(string format, object arg1, object arg2);
method DebugFormat (line 154) | void DebugFormat(string format, object arg1, object arg2, object arg3);
method DebugFormat (line 161) | void DebugFormat(string format, params object[] args);
method DebugFormat (line 170) | void DebugFormat(IFormatProvider provider, string format, params objec...
method Info (line 178) | void Info(object message);
method Info (line 186) | void Info(object message, Exception exception);
method InfoFormat (line 193) | void InfoFormat(string format, object arg);
method InfoFormat (line 201) | void InfoFormat(string format, object arg1, object arg2);
method InfoFormat (line 210) | void InfoFormat(string format, object arg1, object arg2, object arg3);
method InfoFormat (line 217) | void InfoFormat(string format, params object[] args);
method InfoFormat (line 226) | void InfoFormat(IFormatProvider provider, string format, params object...
method Warn (line 234) | void Warn(object message);
method Warn (line 242) | void Warn(object message, Exception exception);
method WarnFormat (line 249) | void WarnFormat(string format, object arg);
method WarnFormat (line 257) | void WarnFormat(string format, object arg1, object arg2);
method WarnFormat (line 266) | void WarnFormat(string format, object arg1, object arg2, object arg3);
method WarnFormat (line 273) | void WarnFormat(string format, params object[] args);
method WarnFormat (line 282) | void WarnFormat(IFormatProvider provider, string format, params object...
method Error (line 290) | void Error(object message);
method Error (line 298) | void Error(object message, Exception exception);
method ErrorFormat (line 305) | void ErrorFormat(string format, object arg);
method ErrorFormat (line 313) | void ErrorFormat(string format, object arg1, object arg2);
method ErrorFormat (line 322) | void ErrorFormat(string format, object arg1, object arg2, object arg3);
method ErrorFormat (line 329) | void ErrorFormat(string format, params object[] args);
method ErrorFormat (line 338) | void ErrorFormat(IFormatProvider provider, string format, params objec...
method Fatal (line 346) | void Fatal(object message);
method Fatal (line 354) | void Fatal(object message, Exception exception);
method FatalFormat (line 361) | void FatalFormat(string format, object arg);
method FatalFormat (line 369) | void FatalFormat(string format, object arg1, object arg2);
method FatalFormat (line 378) | void FatalFormat(string format, object arg1, object arg2, object arg3);
method FatalFormat (line 385) | void FatalFormat(string format, params object[] args);
method FatalFormat (line 394) | void FatalFormat(IFormatProvider provider, string format, params objec...
type IEasyLogger (line 403) | public interface IEasyLogger<T> : IEasyLogger { }
method GetScopedLogger (line 21) | IDisposable GetScopedLogger(string name);
method Trace (line 66) | void Trace(object message);
method Trace (line 74) | void Trace(object message, Exception exception);
method TraceFormat (line 81) | void TraceFormat(string format, object arg);
method TraceFormat (line 89) | void TraceFormat(string format, object arg1, object arg2);
method TraceFormat (line 98) | void TraceFormat(string format, object arg1, object arg2, object arg3);
method TraceFormat (line 105) | void TraceFormat(string format, params object[] args);
method TraceFormat (line 114) | void TraceFormat(IFormatProvider provider, string format, params objec...
method Debug (line 122) | void Debug(object message);
method Debug (line 130) | void Debug(object message, Exception exception);
method DebugFormat (line 137) | void DebugFormat(string format, object arg);
method DebugFormat (line 145) | void DebugFormat(string format, object arg1, object arg2);
method DebugFormat (line 154) | void DebugFormat(string format, object arg1, object arg2, object arg3);
method DebugFormat (line 161) | void DebugFormat(string format, params object[] args);
method DebugFormat (line 170) | void DebugFormat(IFormatProvider provider, string format, params objec...
method Info (line 178) | void Info(object message);
method Info (line 186) | void Info(object message, Exception exception);
method InfoFormat (line 193) | void InfoFormat(string format, object arg);
method InfoFormat (line 201) | void InfoFormat(string format, object arg1, object arg2);
method InfoFormat (line 210) | void InfoFormat(string format, object arg1, object arg2, object arg3);
method InfoFormat (line 217) | void InfoFormat(string format, params object[] args);
method InfoFormat (line 226) | void InfoFormat(IFormatProvider provider, string format, params object...
method Warn (line 234) | void Warn(object message);
method Warn (line 242) | void Warn(object message, Exception exception);
method WarnFormat (line 249) | void WarnFormat(string format, object arg);
method WarnFormat (line 257) | void WarnFormat(string format, object arg1, object arg2);
method WarnFormat (line 266) | void WarnFormat(string format, object arg1, object arg2, object arg3);
method WarnFormat (line 273) | void WarnFormat(string format, params object[] args);
method WarnFormat (line 282) | void WarnFormat(IFormatProvider provider, string format, params object...
method Error (line 290) | void Error(object message);
method Error (line 298) | void Error(object message, Exception exception);
method ErrorFormat (line 305) | void ErrorFormat(string format, object arg);
method ErrorFormat (line 313) | void ErrorFormat(string format, object arg1, object arg2);
method ErrorFormat (line 322) | void ErrorFormat(string format, object arg1, object arg2, object arg3);
method ErrorFormat (line 329) | void ErrorFormat(string format, params object[] args);
method ErrorFormat (line 338) | void ErrorFormat(IFormatProvider provider, string format, params objec...
method Fatal (line 346) | void Fatal(object message);
method Fatal (line 354) | void Fatal(object message, Exception exception);
method FatalFormat (line 361) | void FatalFormat(string format, object arg);
method FatalFormat (line 369) | void FatalFormat(string format, object arg1, object arg2);
method FatalFormat (line 378) | void FatalFormat(string format, object arg1, object arg2, object arg3);
method FatalFormat (line 385) | void FatalFormat(string format, params object[] args);
method FatalFormat (line 394) | void FatalFormat(IFormatProvider provider, string format, params objec...
FILE: Easy.Logger.Interfaces/ILogService.cs
type ILogService (line 10) | public interface ILogService : IDisposable
method Configure (line 20) | void Configure(FileInfo configFile);
method GetLogger (line 27) | IEasyLogger GetLogger(string loggerName);
method GetLogger (line 34) | IEasyLogger GetLogger(Type loggerType);
method GetLogger (line 41) | IEasyLogger GetLogger<T>();
FILE: Easy.Logger.Tests.Integration/Context.cs
class Context (line 17) | [TestFixture]
method Setup (line 25) | [SetUp]
method Run (line 42) | [Test]
method TestTearDown (line 82) | [TearDown]
method CheckLogFileContent (line 92) | private static void CheckLogFileContent(FileSystemInfo logFile)
method CheckReceivedPayloads (line 115) | private static void CheckReceivedPayloads(LogPayload[] payloads)
method StartHttpServer (line 232) | private void StartHttpServer()
FILE: Easy.Logger.Tests.Integration/EasyLogListener.cs
class EasyLogListener (line 16) | public sealed class EasyLogListener : IDisposable
method EasyLogListener (line 46) | public EasyLogListener(Uri prefix)
method Start (line 63) | public void Start() => StartListening();
method Dispose (line 68) | public void Dispose()
method StartListening (line 76) | private async void StartListening()
method Deserialize (line 122) | private LogPayload Deserialize(Stream stream)
method HandleException (line 131) | private void HandleException(Exception e)
method IsValidRequest (line 140) | private static bool IsValidRequest(HttpListenerRequest req)
method IsJSONMediaType (line 146) | private static bool IsJSONMediaType(string contentType) =>
FILE: Easy.Logger.Tests.Integration/Models/LogPayload.cs
class LogPayload (line 11) | public sealed class LogPayload
method ToString (line 53) | public override string ToString()
class LogEntry (line 86) | public class LogEntry
FILE: Easy.Logger.Tests.Unit/AsyncBufferingForwardingAppenderTests.cs
class AsyncBufferingForwardingAppenderTests (line 11) | [TestFixture]
method When_testing_a_non_lossy_forwarder (line 14) | [Test]
method When_testing_a_lossy_forwarder (line 60) | [Test]
FILE: Easy.Logger.Tests.Unit/CreatingNewLog4NetServiceTests.cs
class CreatingNewLog4NetServiceTests (line 13) | [TestFixture]
method When_creating_a_log4net_service_with_default_configuration (line 19) | [Test]
method When_creating_a_log4net_service_with_default_configuration_and_then_confuguring_it_again (line 61) | [Test]
method ExtractSampleApp (line 137) | private static DirectoryInfo ExtractSampleApp()
method GetLog4NetConfiguration (line 152) | private static string GetLog4NetConfiguration()
FILE: Easy.Logger.Tests.Unit/HTTPAppenderTests.cs
class HTTPAppenderTests (line 10) | [TestFixture]
method When_creating_appender_without_name (line 13) | [Test]
method When_activating_appender (line 29) | [Test]
method When_activating_appender_with_invalid_endpoint (line 46) | [Test]
method When_logging_events_through_appender (line 68) | [Test]
FILE: Easy.Logger.Tests.Unit/Helpers/ProcessHelper.cs
class ProcessHelper (line 7) | internal static class ProcessHelper
method GetProcess (line 9) | public static Process GetProcess(string processPath, IList<string> out...
FILE: Easy.Logger.Tests.Unit/Log4NetLoggerTests.cs
class Log4NetLoggerTests (line 12) | [TestFixture]
method SetUp (line 19) | [OneTimeSetUp]
method When_logging_using_trace (line 38) | [Test]
method When_logging_using_debug (line 66) | [Test]
method When_logging_using_info (line 94) | [Test]
method When_logging_using_warn (line 122) | [Test]
method When_logging_using_error (line 150) | [Test]
method When_logging_using_fatal (line 178) | [Test]
method When_checking_logger_levels (line 206) | [Test]
FILE: Easy.Logger/AsyncBufferingForwardingAppender.cs
class AsyncBufferingForwardingAppender (line 13) | public sealed class AsyncBufferingForwardingAppender : BufferingForwardi...
method AsyncBufferingForwardingAppender (line 52) | public AsyncBufferingForwardingAppender()
method ActivateOptions (line 62) | public override void ActivateOptions()
method SendBuffer (line 78) | protected override void SendBuffer(LoggingEvent[] events)
method OnClose (line 92) | protected override void OnClose()
method Process (line 102) | private void Process(LoggingEvent[] logEvents)
method InvokeFlushIfIdle (line 111) | private void InvokeFlushIfIdle(object _)
method LogWarningIfLossy (line 119) | private void LogWarningIfLossy()
FILE: Easy.Logger/Log4NetLogger.cs
class Log4NetLogger (line 14) | public sealed class Log4NetLogger<T> : Log4NetLogger, IEasyLogger<T>
method Log4NetLogger (line 17) | static Log4NetLogger() => _ = Log4NetService.Instance;
method Log4NetLogger (line 23) | public Log4NetLogger() : base(LogManager.GetLogger(typeof(T))) {}
method Log4NetLogger (line 38) | protected internal Log4NetLogger(ILog logger) => _logger = logger;
method GetScopedLogger (line 50) | public IDisposable GetScopedLogger(string name) => new Scope(name);
method Trace (line 112) | [DebuggerStepThrough]
method Trace (line 121) | [DebuggerStepThrough]
method TraceFormat (line 140) | [DebuggerStepThrough]
method TraceFormat (line 156) | [DebuggerStepThrough]
method TraceFormat (line 176) | [DebuggerStepThrough]
method TraceFormat (line 198) | [DebuggerStepThrough]
method TraceFormat (line 221) | [DebuggerStepThrough]
method Debug (line 249) | [DebuggerStepThrough]
method Debug (line 258) | [DebuggerStepThrough]
method DebugFormat (line 277) | [DebuggerStepThrough]
method DebugFormat (line 293) | [DebuggerStepThrough]
method DebugFormat (line 313) | [DebuggerStepThrough]
method DebugFormat (line 335) | [DebuggerStepThrough]
method DebugFormat (line 358) | [DebuggerStepThrough]
method Info (line 387) | [DebuggerStepThrough]
method Info (line 396) | [DebuggerStepThrough]
method InfoFormat (line 415) | [DebuggerStepThrough]
method InfoFormat (line 431) | [DebuggerStepThrough]
method InfoFormat (line 451) | [DebuggerStepThrough]
method InfoFormat (line 473) | [DebuggerStepThrough]
method InfoFormat (line 496) | [DebuggerStepThrough]
method Warn (line 525) | [DebuggerStepThrough]
method Warn (line 534) | [DebuggerStepThrough]
method WarnFormat (line 553) | [DebuggerStepThrough]
method WarnFormat (line 569) | [DebuggerStepThrough]
method WarnFormat (line 589) | [DebuggerStepThrough]
method WarnFormat (line 611) | [DebuggerStepThrough]
method WarnFormat (line 634) | [DebuggerStepThrough]
method Error (line 662) | [DebuggerStepThrough]
method Error (line 671) | [DebuggerStepThrough]
method ErrorFormat (line 690) | [DebuggerStepThrough]
method ErrorFormat (line 706) | [DebuggerStepThrough]
method ErrorFormat (line 726) | [DebuggerStepThrough]
method ErrorFormat (line 748) | [DebuggerStepThrough]
method ErrorFormat (line 771) | [DebuggerStepThrough]
method Fatal (line 800) | [DebuggerStepThrough]
method Fatal (line 809) | [DebuggerStepThrough]
method FatalFormat (line 828) | [DebuggerStepThrough]
method FatalFormat (line 844) | [DebuggerStepThrough]
method FatalFormat (line 864) | [DebuggerStepThrough]
method FatalFormat (line 886) | [DebuggerStepThrough]
method FatalFormat (line 909) | [DebuggerStepThrough]
method PrefixScopesIfAny (line 918) | private static string PrefixScopesIfAny(string message)
method IsEnabledFor (line 924) | private bool IsEnabledFor(Level level) => _logger.Logger.IsEnabledFor(...
method LogImpl (line 926) | private void LogImpl(Level level, object message, Exception exception)
class Log4NetLogger (line 29) | public class Log4NetLogger : IEasyLogger
method Log4NetLogger (line 17) | static Log4NetLogger() => _ = Log4NetService.Instance;
method Log4NetLogger (line 23) | public Log4NetLogger() : base(LogManager.GetLogger(typeof(T))) {}
method Log4NetLogger (line 38) | protected internal Log4NetLogger(ILog logger) => _logger = logger;
method GetScopedLogger (line 50) | public IDisposable GetScopedLogger(string name) => new Scope(name);
method Trace (line 112) | [DebuggerStepThrough]
method Trace (line 121) | [DebuggerStepThrough]
method TraceFormat (line 140) | [DebuggerStepThrough]
method TraceFormat (line 156) | [DebuggerStepThrough]
method TraceFormat (line 176) | [DebuggerStepThrough]
method TraceFormat (line 198) | [DebuggerStepThrough]
method TraceFormat (line 221) | [DebuggerStepThrough]
method Debug (line 249) | [DebuggerStepThrough]
method Debug (line 258) | [DebuggerStepThrough]
method DebugFormat (line 277) | [DebuggerStepThrough]
method DebugFormat (line 293) | [DebuggerStepThrough]
method DebugFormat (line 313) | [DebuggerStepThrough]
method DebugFormat (line 335) | [DebuggerStepThrough]
method DebugFormat (line 358) | [DebuggerStepThrough]
method Info (line 387) | [DebuggerStepThrough]
method Info (line 396) | [DebuggerStepThrough]
method InfoFormat (line 415) | [DebuggerStepThrough]
method InfoFormat (line 431) | [DebuggerStepThrough]
method InfoFormat (line 451) | [DebuggerStepThrough]
method InfoFormat (line 473) | [DebuggerStepThrough]
method InfoFormat (line 496) | [DebuggerStepThrough]
method Warn (line 525) | [DebuggerStepThrough]
method Warn (line 534) | [DebuggerStepThrough]
method WarnFormat (line 553) | [DebuggerStepThrough]
method WarnFormat (line 569) | [DebuggerStepThrough]
method WarnFormat (line 589) | [DebuggerStepThrough]
method WarnFormat (line 611) | [DebuggerStepThrough]
method WarnFormat (line 634) | [DebuggerStepThrough]
method Error (line 662) | [DebuggerStepThrough]
method Error (line 671) | [DebuggerStepThrough]
method ErrorFormat (line 690) | [DebuggerStepThrough]
method ErrorFormat (line 706) | [DebuggerStepThrough]
method ErrorFormat (line 726) | [DebuggerStepThrough]
method ErrorFormat (line 748) | [DebuggerStepThrough]
method ErrorFormat (line 771) | [DebuggerStepThrough]
method Fatal (line 800) | [DebuggerStepThrough]
method Fatal (line 809) | [DebuggerStepThrough]
method FatalFormat (line 828) | [DebuggerStepThrough]
method FatalFormat (line 844) | [DebuggerStepThrough]
method FatalFormat (line 864) | [DebuggerStepThrough]
method FatalFormat (line 886) | [DebuggerStepThrough]
method FatalFormat (line 909) | [DebuggerStepThrough]
method PrefixScopesIfAny (line 918) | private static string PrefixScopesIfAny(string message)
method IsEnabledFor (line 924) | private bool IsEnabledFor(Level level) => _logger.Logger.IsEnabledFor(...
method LogImpl (line 926) | private void LogImpl(Level level, object message, Exception exception)
FILE: Easy.Logger/Log4NetService.cs
class Log4NetService (line 15) | public sealed class Log4NetService : ILogService
method Log4NetService (line 34) | private Log4NetService()
method Configure (line 69) | public void Configure(FileInfo configFile)
method GetLogger (line 92) | [DebuggerStepThrough]
method GetLogger (line 105) | [DebuggerStepThrough]
method GetLogger (line 118) | [DebuggerStepThrough]
method Dispose (line 128) | [DebuggerStepThrough]
method ConfigureImpl (line 131) | private void ConfigureImpl(ILoggerRepository repository, FileInfo conf...
method EnsureConfigured (line 139) | private void EnsureConfigured()
FILE: Easy.Logger/Scope.cs
type Scope (line 10) | public struct Scope : IDisposable
method Scope (line 18) | internal Scope(string name) => Stacks[ContextName].Push(name);
method Dispose (line 25) | public void Dispose() => Stacks[ContextName].Pop();
method ToString (line 31) | public override string ToString() => ScopeMessage;
FILE: Easy.Logger/Sequencer.cs
class Sequencer (line 12) | internal sealed class Sequencer<T>
method Sequencer (line 22) | public Sequencer(Action<T> consumer) : this(-1, consumer) {}
method Sequencer (line 32) | public Sequencer(Action<T> consumer, int boundedCapacity) : this(bound...
method Sequencer (line 40) | private Sequencer(int boundedCapacity, Action<T> consumer)
method Enqueue (line 83) | public void Enqueue(T item)
method TryEnqueue (line 103) | public bool TryEnqueue(T item)
method TryEnqueue (line 126) | public bool TryEnqueue(T item, TimeSpan timeout)
method Shutdown (line 145) | public void Shutdown(bool waitForPendingItems = true)
method GetConsumer (line 163) | private Task GetConsumer(Action<T> consumer)
class SequencerExceptionEventArgs (line 190) | internal sealed class SequencerExceptionEventArgs : EventArgs
method SequencerExceptionEventArgs (line 196) | public SequencerExceptionEventArgs(SequencerException e) => Exception ...
class SequencerException (line 207) | internal sealed class SequencerException : Exception
method SequencerException (line 212) | public SequencerException() { }
method SequencerException (line 218) | public SequencerException(string message) : base(message) { }
method SequencerException (line 225) | public SequencerException(string message, Exception innerException) : ...
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (219K chars).
[
{
"path": ".gitattributes",
"chars": 2518,
"preview": "###############################################################################\n# Set default behavior to automatically "
},
{
"path": ".gitignore",
"chars": 3130,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User"
},
{
"path": "Easy.Logger/AsyncBufferingForwardingAppender.cs",
"chars": 4586,
"preview": "namespace Easy.Logger\n{\n using System;\n using System.Threading;\n using log4net.Appender;\n using log4net.Cor"
},
{
"path": "Easy.Logger/Easy.Logger.csproj",
"chars": 2269,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <PackageId>Easy.Logger</PackageId>\n <Description>A modern h"
},
{
"path": "Easy.Logger/Log4NetLogger.cs",
"chars": 47413,
"preview": "namespace Easy.Logger\n{\n using System;\n using System.Diagnostics;\n using System.Globalization;\n using Easy."
},
{
"path": "Easy.Logger/Log4NetService.cs",
"chars": 6241,
"preview": "namespace Easy.Logger\n{\n using System;\n using System.Diagnostics;\n using System.IO;\n using System.Reflectio"
},
{
"path": "Easy.Logger/Properties/AssemblyInfo.cs",
"chars": 154,
"preview": "using System.Runtime.CompilerServices;\n\n[assembly: InternalsVisibleTo(\"Easy.Logger.Extensions\")]\n[assembly: InternalsVi"
},
{
"path": "Easy.Logger/Scope.cs",
"chars": 1007,
"preview": "namespace Easy.Logger\n{\n using System;\n using log4net;\n using log4net.Util;\n\n /// <summary>\n /// Provide"
},
{
"path": "Easy.Logger/Sequencer.cs",
"chars": 8543,
"preview": "namespace Easy.Logger\n{\n using System;\n using System.Collections.Concurrent;\n using System.Threading;\n usin"
},
{
"path": "Easy.Logger/sample-log4net.config",
"chars": 4356,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n <root>\n <!--\n 1.OFF - nothing gets logged\n 2.FATAL \n 3."
},
{
"path": "Easy.Logger.Benchmarker/App.config",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n <runtime>\n <gcServer enabled=\"true\"/>\n </runtime>\n\n <start"
},
{
"path": "Easy.Logger.Benchmarker/Easy.Logger.Benchmarker.csproj",
"chars": 2860,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbui"
},
{
"path": "Easy.Logger.Benchmarker/Program.cs",
"chars": 3861,
"preview": "namespace Easy.Logger.Benchmarker\n{\n using System;\n using System.Diagnostics;\n using System.IO;\n using Syst"
},
{
"path": "Easy.Logger.Benchmarker/Properties/AssemblyInfo.cs",
"chars": 1375,
"preview": "using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled "
},
{
"path": "Easy.Logger.Benchmarker/the-log4net.config",
"chars": 1359,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n <root>\n <level value=\"ALL\"/>\n <appender-ref ref=\"AsyncBufferi"
},
{
"path": "Easy.Logger.Benchmarker.Core/Easy.Logger.Benchmarker.Core.csproj",
"chars": 599,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net6.0</Targ"
},
{
"path": "Easy.Logger.Benchmarker.Core/Program.cs",
"chars": 3455,
"preview": "namespace Easy.Logger.Benchmarker.Core\n{\n using System;\n using System.Diagnostics;\n using System.Threading;\n "
},
{
"path": "Easy.Logger.Benchmarker.Core/log4net.config",
"chars": 1123,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n <root>\n <level value=\"ALL\"/>\n <appender-ref ref=\"AsyncBufferi"
},
{
"path": "Easy.Logger.Extensions/Easy.Logger.Extensions.csproj",
"chars": 1656,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <PackageId>Easy.Logger.Extensions</PackageId>\n <Description"
},
{
"path": "Easy.Logger.Extensions/HTTPAppender.cs",
"chars": 7596,
"preview": "namespace Easy.Logger.Extensions\n{\n using System;\n using System.Diagnostics;\n using System.Diagnostics.CodeAna"
},
{
"path": "Easy.Logger.Extensions/Properties/AssemblyInfo.cs",
"chars": 97,
"preview": "using System.Runtime.CompilerServices;\n\n[assembly: InternalsVisibleTo(\"Easy.Logger.Tests.Unit\")]"
},
{
"path": "Easy.Logger.Extensions.Microsoft/Easy.Logger.Extensions.Microsoft.csproj",
"chars": 1412,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <PackageId>Easy.Logger.Extensions.Microsoft</PackageId>\n <D"
},
{
"path": "Easy.Logger.Extensions.Microsoft/EasyLogger.cs",
"chars": 3716,
"preview": "namespace Easy.Logger.Extensions.Microsoft\n{\n using System;\n using Easy.Logger.Interfaces;\n using global::Micr"
},
{
"path": "Easy.Logger.Extensions.Microsoft/EasyLoggerConfig.cs",
"chars": 631,
"preview": "namespace Easy.Logger.Extensions.Microsoft\n{\n using System.IO;\n\n /// <summary>\n /// An abstraction for configu"
},
{
"path": "Easy.Logger.Extensions.Microsoft/EasyLoggerProvider.cs",
"chars": 1658,
"preview": "namespace Easy.Logger.Extensions.Microsoft\n{\n using System.Collections.Concurrent;\n using System.Diagnostics.Code"
},
{
"path": "Easy.Logger.Extensions.Microsoft/IEasyLoggerConfig.cs",
"chars": 369,
"preview": "namespace Easy.Logger.Extensions.Microsoft\n{\n using System.IO;\n\n /// <summary>\n /// Specifies the contract for"
},
{
"path": "Easy.Logger.Extensions.Microsoft/LoggingBuilderExtensions.cs",
"chars": 1185,
"preview": "namespace Easy.Logger.Extensions.Microsoft\n{\n using System.IO;\n using global::Microsoft.Extensions.DependencyInje"
},
{
"path": "Easy.Logger.Extensions.Microsoft/README.md",
"chars": 1144,
"preview": "### Setting up in ASP.NET Core:\n\nThe current implementation is based on [log4net](https://logging.apache.org/log4net/)."
},
{
"path": "Easy.Logger.Interfaces/Easy.Logger.Interfaces.csproj",
"chars": 1398,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n \n <PropertyGroup>\n <PackageId>Easy.Logger.Interfaces</PackageId>\n <Descripti"
},
{
"path": "Easy.Logger.Interfaces/IEasyLogger.cs",
"chars": 19885,
"preview": "namespace Easy.Logger.Interfaces\n{\n using System;\n\n /// <summary>\n /// The <see cref=\"IEasyLogger\"/> interface"
},
{
"path": "Easy.Logger.Interfaces/ILogService.cs",
"chars": 1762,
"preview": "namespace Easy.Logger.Interfaces\n{\n using System;\n using System.IO;\n\n /// <summary>\n /// The <see cref=\"ILo"
},
{
"path": "Easy.Logger.Tests.Integration/Context.cs",
"chars": 11802,
"preview": "namespace Easy.Logger.Tests.Integration\n{\n using System;\n using System.Collections.Concurrent;\n using System.D"
},
{
"path": "Easy.Logger.Tests.Integration/Easy.Logger.Tests.Integration.csproj",
"chars": 839,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>net8.0</TargetFramework>\n </PropertyGroup>\n"
},
{
"path": "Easy.Logger.Tests.Integration/EasyLogListener.cs",
"chars": 5565,
"preview": "namespace Easy.Logger.Tests.Integration\n{\n using System;\n using System.IO;\n using System.Net;\n using System"
},
{
"path": "Easy.Logger.Tests.Integration/Models/LogPayload.cs",
"chars": 3347,
"preview": "// ReSharper disable InconsistentNaming\nnamespace Easy.Logger.Tests.Integration.Models\n{\n using System;\n using Sy"
},
{
"path": "Easy.Logger.Tests.Integration/integration.tests-log4net.config",
"chars": 1075,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n <root>\n <level value=\"ALL\"/>\n <appender-ref ref=\"AsyncBufferi"
},
{
"path": "Easy.Logger.Tests.Unit/AsyncBufferingForwardingAppenderTests.cs",
"chars": 5023,
"preview": "namespace Easy.Logger.Tests.Unit\n{\n using System.Collections.Generic;\n using System.Threading.Tasks;\n using lo"
},
{
"path": "Easy.Logger.Tests.Unit/CreatingNewLog4NetServiceTests.cs",
"chars": 7580,
"preview": "namespace Easy.Logger.Tests.Unit\n{\n using System;\n using System.Collections.Generic;\n using System.IO;\n usi"
},
{
"path": "Easy.Logger.Tests.Unit/Easy.Logger.Tests.Unit.csproj",
"chars": 936,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>net8.0</TargetFramework>\n </PropertyGroup>\n "
},
{
"path": "Easy.Logger.Tests.Unit/HTTPAppenderTests.cs",
"chars": 2653,
"preview": "namespace Easy.Logger.Tests.Unit\n{\n using System;\n using System.Threading.Tasks;\n using Easy.Logger.Extensions"
},
{
"path": "Easy.Logger.Tests.Unit/Helpers/ProcessHelper.cs",
"chars": 1710,
"preview": "namespace Easy.Logger.Tests.Unit.Helpers\n{\n using System;\n using System.Collections.Generic;\n using System.Dia"
},
{
"path": "Easy.Logger.Tests.Unit/Log4NetLoggerTests.cs",
"chars": 11172,
"preview": "namespace Easy.Logger.Tests.Unit\n{\n using System;\n using System.Globalization;\n using Easy.Logger.Interfaces;\n"
},
{
"path": "Easy.Logger.sln",
"chars": 5658,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2910"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Nima Ara\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "NuGet-Pack-Easy.Logger.Extensions.Microsoft.bat",
"chars": 274,
"preview": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Extensions.Microsoft\ndotnet pack .\\Easy.Logger.Extensions."
},
{
"path": "NuGet-Pack-Easy.Logger.Extensions.bat",
"chars": 244,
"preview": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Extensions\ndotnet pack .\\Easy.Logger.Extensions\\Easy.Logge"
},
{
"path": "NuGet-Pack-Easy.Logger.Interfaces.bat",
"chars": 244,
"preview": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Interfaces\ndotnet pack .\\Easy.Logger.Interfaces\\Easy.Logge"
},
{
"path": "NuGet-Pack-Easy.Logger.bat",
"chars": 211,
"preview": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger\ndotnet pack .\\Easy.Logger\\Easy.Logger.csproj --output .\\nu"
},
{
"path": "README.md",
"chars": 11040,
"preview": "[](https://ci.appveyor.com/project"
}
]
About this extraction
This page contains the full source code of the NimaAra/Easy.Logger GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (203.2 KB), approximately 49.9k tokens, and a symbol index with 223 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.