Simplifies logging through a static class and some IL manipulation
See Milestones for release notes.
This is an add-in for Fody
It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.
- Catel
- Custom (for frameworks/toolkits with custom logging)
- CommonLogging
- NLog
- NServiceBus
- Serilog
- Splat
See also Fody usage.
Install the Anotar.xxx.Fody NuGet package and update the Fody NuGet package:
PM> Install-Package Fody
PM> Install-Package Anotar.xxx.Fody
The Install-Package Fody
is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.
Add <Anotar.xxx/>
to FodyWeavers.xml
<Weavers>
<Anotar.xxx/>
</Weavers>
- Catel package http://nuget.org/packages/Anotar.Catel.Fody
- CommonLogging package http://nuget.org/packages/Anotar.CommonLogging.Fody
- Custom package http://nuget.org/packages/Anotar.Custom.Fody
- NLog package http://nuget.org/packages/Anotar.NLog.Fody
- NServiceBus package http://nuget.org/packages/Anotar.NServiceBus.Fody
- Serilog package http://nuget.org/packages/Anotar.Serilog.Fody
- Splat package http://nuget.org/packages/Anotar.Splat.Fody
public class MyClass
{
void MyMethod()
{
LogTo.Debug("TheMessage");
}
}
public class MyClass
{
static ILog logger = LogManager.GetLogger(typeof(MyClass));
void MyMethod()
{
logger.WriteWithData("Method: 'Void MyMethod()'. Line: ~12. TheMessage", null, LogEvent.Debug);
}
}
public class MyClass
{
static ILog logger = LoggerManager.GetLogger("MyClass");
void MyMethod()
{
logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
public class MyClass
{
static ILogger AnotarLogger = LoggerFactory.GetLogger<MyClass>();
void MyMethod()
{
AnotarLogger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
public class MyClass
{
static Logger logger = LogManager.GetLogger("MyClass");
void MyMethod()
{
logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
public class MyClass
{
static ILog logger = LogManager.GetLogger("MyClass");
void MyMethod()
{
logger.DebugFormat("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
public class MyClass
{
static ILogger logger = Log.ForContext<MyClass>();
void MyMethod()
{
if (logger.IsEnabled(LogEventLevel.Debug))
{
logger
.ForContext("MethodName", "Void MyMethod()")
.ForContext("LineNumber", 8)
.Debug("TheMessage");
}
}
}
public class MyClass
{
static IFullLogger logger = ((ILogManager) Locator.Current.GetService(typeof(ILogManager), null))
.GetLogger(typeof(ClassWithLogging));
void MyMethod()
{
logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
There are also appropriate methods for Warn, Info, Error etc as applicable to each of the logging frameworks.
Each of these methods has the expected 'message', 'params' and 'exception' overloads.
The LogTo
class also has IsLevelEnabled
properties that redirect to the respective level enabled checks in each framework.
public class MyClass
{
void MyMethod()
{
if (LogTo.IsDebugEnabled)
{
LogTo.Debug("TheMessage");
}
}
}
public class MyClass
{
static Logger logger = LogManager.GetLogger("MyClass");
void MyMethod()
{
if (logger.IsDebugEnabled)
{
logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
}
}
}
All the LogTo
methods have equivalent overloads that accept a Func<string>
instead of a string. This delegate is used to construct the message and should be used when that message construction is resource intensive. At compile time the logging will be wrapped in a IsEnabled
check so as to only incur the cost if that level of logging is required.
public class MyClass
{
void MyMethod()
{
LogTo.Debug(()=>"TheMessage");
}
}
public class MyClass
{
static Logger logger = LogManager.GetLogger("MyClass");
void MyMethod()
{
if (logger.IsDebugEnabled)
{
Func<string> messageConstructor = () => "TheMessage";
logger.Debug("Method: 'Void DebugStringFunc()'. Line: ~58. " + messageConstructor());
}
}
}
[LogToErrorOnException]
void MyMethod(string param1, int param2)
{
//Do Stuff
}
void MyMethod(string param1, int param2)
{
try
{
//Do Stuff
}
catch (Exception exception)
{
if (logger.IsErrorEnabled)
{
var message = string.Format("Exception occurred in SimpleClass.MyMethod. param1 '{0}', param2 '{1}'", param1, param2);
logger.ErrorException(message, exception);
}
throw;
}
}
The custom logging variant exist for several reasons
- Projects targeting an obscure logging libraries i.e. not NLog or SeriLog. Or wraps a logging library with a custom API.
- Projects that have their own logging custom logging libraries
- Projects that support multiple different logging libraries
It works by allowing you to have custom logger construction and a custom logger instance.
The Logger Factory is responsible for building an instance of a logger.
- Named
LoggerFactory
. - Namespace doesn't matter.
- Have a static method GetLogger.
For example
public class LoggerFactory
{
public static Logger GetLogger<T>()
{
return new Logger();
}
}
The Logger instance is responsible for building an instance of a logger.
- Name doesn't matter. It will be derived from the return type of
LoggerFactory.GetLogger
. - Must not be generic.
- Namespace doesn't matter.
- Can be either an interface, a concrete class or an abstract class.
- Can contain the members listed below. All members are optional. However an build error will be thrown if you attempt to use one of the members that doesn't exist. So for example if you call
LogTo.Debug
andLogger.Debug
(with the same parameters) doesn't.
For example
public class Logger
{
public void Trace(string message){}
public void Trace(string format, params object[] args){}
public void Trace(Exception exception, string format, params object[] args){}
public bool IsTraceEnabled { get; private set; }
public void Debug(string message){}
public void Debug(string format, params object[] args){}
public void Debug(Exception exception, string format, params object[] args){}
public bool IsDebugEnabled { get; private set; }
public void Information(string message){}
public void Information(string format, params object[] args){}
public void Information(Exception exception, string format, params object[] args){}
public bool IsInformationEnabled { get; private set; }
public void Warning(string message){}
public void Warning(string format, params object[] args){}
public void Warning(Exception exception, string format, params object[] args){}
public bool IsWarningEnabled { get; private set; }
public void Error(string message){}
public void Error(string format, params object[] args){}
public void Error(Exception exception, string format, params object[] args){}
public bool IsErrorEnabled { get; private set; }
public void Fatal(string message){}
public void Fatal(string format, params object[] args){}
public void Fatal(Exception exception, string format, params object[] args){}
public bool IsFatalEnabled { get; private set; }
}
If LoggerFactory
and Logger
exist in the current assembly they will be picked up automatically.
If LoggerFactory
and Logger
exist in a different assembly You will need to use a [LoggerFactoryAttribute]
to tell Anotar where to look.
[assembly: LoggerFactoryAttribute(typeof(MyUtilsLibrary.LoggerFactory))]
After compilation the reference to the Anotar assemblies will be removed so you don't need to deploy the assembly.
When I am coding I often want to quickly add a line of logging code. If I don't already have the static logger
field I have to jump back to the top of the file to add it. This breaks my train of thought. I know this is minor but it is still an annoyance. Static logging methods are much less disruptive to call.
Often when I am logging I want to know the method and line number I am logging from. I don't want to manually add this. So using IL I just prefix the message with the method name and line number. Note that the line number is prefixed with '~'. The reason for this is that a single line of code can equate to multiple IL instructions. So I walk back up the instructions until I find one that has a line number and use that. Hence it is an approximation.
If you don't want the extra information, method name and line number, then add this to AssemblyInfo.cs
:
[assembly: LogMinimalMessage]
The CallerInfoAttributes consist of CallerLineNumberAttribute, CallerFilePathAttribute and CallerMemberNameAttribute. The allow you to pass information about the caller method to the callee method.
So some of this could be achieved using these attributes however there are a couple of points that complicate things.
So this makes it a little difficult to use with other runtimes.
Logging APIs all make use of params
to pass arguments to a string.Format
. Since you can't use params
with CallerInfoAttributes most logging APIs choose not to use these attributes.
Icon courtesy of The Noun Project