• Stars
    star
    262
  • Rank 156,136 (Top 4 %)
  • Language
    C#
  • License
    MIT License
  • Created about 12 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simplifies logging through a static class and some IL manipulation.

Anotar.Fody

Chat on Gitter

Simplifies logging through a static class and some IL manipulation

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.

Supported Logging Libraries

Usage

See also Fody usage.

NuGet installation

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 to FodyWeavers.xml

Add <Anotar.xxx/> to FodyWeavers.xml

<Weavers>
  <Anotar.xxx/>
</Weavers>

NuGets

Explicit Logging

Your Code

public class MyClass
{
    void MyMethod()
    {
        LogTo.Debug("TheMessage");
    }
}

What gets compiled

In Catel

public class MyClass
{
    static ILog logger = LogManager.GetLogger(typeof(MyClass));

    void MyMethod()
    {
        logger.WriteWithData("Method: 'Void MyMethod()'. Line: ~12. TheMessage", null, LogEvent.Debug);
    }
}

In CommonLogging

public class MyClass
{
    static ILog logger = LoggerManager.GetLogger("MyClass");

    void MyMethod()
    {
        logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
    }
}

In Custom

public class MyClass
{
    static ILogger AnotarLogger = LoggerFactory.GetLogger<MyClass>();

    void MyMethod()
    {
        AnotarLogger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
    }
}

In NLog

public class MyClass
{
    static Logger logger = LogManager.GetLogger("MyClass");

    void MyMethod()
    {
        logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
    }
}

In NServiceBus

public class MyClass
{
    static ILog logger = LogManager.GetLogger("MyClass");

    void MyMethod()
    {
        logger.DebugFormat("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
    }
}

In Serilog

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");
        }
    }
}

In Splat

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");
    }
}

Other Log Overloads in Explicit Logging

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.

Checking logging level

The LogTo class also has IsLevelEnabled properties that redirect to the respective level enabled checks in each framework.

Your code

public class MyClass
{
    void MyMethod()
    { 
        if (LogTo.IsDebugEnabled)
        {
            LogTo.Debug("TheMessage");
        }
    }
}

What gets compiled

public class MyClass
{
    static Logger logger = LogManager.GetLogger("MyClass");

    void MyMethod()
    {
        if (logger.IsDebugEnabled)
        {
            logger.Debug("Method: 'Void MyMethod()'. Line: ~12. TheMessage");
        }
    }
}

Delegate Logging

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.

Your code

public class MyClass
{
    void MyMethod()
    { 
        LogTo.Debug(()=>"TheMessage");
    }
}

What gets compiled

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());
        }
    }
}

Exception logging

Your code

[LogToErrorOnException]
void MyMethod(string param1, int param2)
{
    //Do Stuff
}

What gets compiled

In NLog

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;
    }
}

Custom logging

The custom logging variant exist for several reasons

  1. Projects targeting an obscure logging libraries i.e. not NLog or SeriLog. Or wraps a logging library with a custom API.
  2. Projects that have their own logging custom logging libraries
  3. Projects that support multiple different logging libraries

It works by allowing you to have custom logger construction and a custom logger instance.

Expected factory and instance formats

Factory

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();
    }
}

Instance

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 and Logger.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; }
}

Discovery

Current Assembly

If LoggerFactory and Logger exist in the current assembly they will be picked up automatically.

Other Assembly

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))]

Nothing to deploy

After compilation the reference to the Anotar assemblies will be removed so you don't need to deploy the assembly.

But why? What purpose does this serve?

1. Don't make me think

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.

2. I want some extra information

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.

I don't want extra information

If you don't want the extra information, method name and line number, then add this to AssemblyInfo.cs:

[assembly: LogMinimalMessage]

Why not use CallerInfoAttributes

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.

1. Only .net 4.5 and up

So this makes it a little difficult to use with other runtimes.

2. Can't be used when passing arrays as params

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

Icon courtesy of The Noun Project

More Repositories

1

Fody

Extensible tool for weaving .net assemblies
C#
4,325
star
2

Costura

Embed references as resources
C#
2,383
star
3

PropertyChanged

Injects INotifyPropertyChanged code into properties at compile time
C#
1,885
star
4

NullGuard

Adds null argument checks to an assembly
C#
686
star
5

MethodTimer

Injects some very basic method timing code.
C#
686
star
6

Home

The landing page for Fody repositories
C#
671
star
7

ConfigureAwait

Configure async code's ConfigureAwait at a global level
C#
444
star
8

MethodDecorator

Compile time decorator pattern via IL rewriting
C#
380
star
9

Equals

Generate Equals, GetHashCode and operators methods from properties.
C#
111
star
10

AsyncErrorHandler

An extension for Fody to integrate error handling into async and TPL code
C#
104
star
11

FodyAddinSamples

A working sample for each Fody Addin
C#
95
star
12

ToString

Generate ToString method from public properties.
C#
82
star
13

Janitor

Simplifies the implementation of IDisposable
C#
76
star
14

Ionad

Replaces static method calls.
C#
68
star
15

InfoOf

Provides methodof, propertyof and fieldof equivalents of typeof .
C#
65
star
16

Virtuosity

Change all members to virtual as part of your build.
C#
61
star
17

PropertyChanging

Injects INotifyPropertyChanging code into properties at compile time.
C#
54
star
18

ExtraConstraints

Facilitates adding constraints for Enum and Delegate to types and methods.
C#
47
star
19

Visualize

Adds debugger attributes to help visualize objects.
C#
43
star
20

Caseless

Change string comparisons to be case insensitive.
C#
40
star
21

Resourcer

Simplifies reading embedded resources from an Assembly.
C#
37
star
22

LoadAssembliesOnStartup

Loads all the references on startup by actually using the types in the module initializer.
C#
36
star
23

Obsolete

Helps keep usages of ObsoleteAttribute consistent.
C#
32
star
24

EmptyConstructor

Adds an empty constructor to classes even if you have a non-empty one defined.
C#
29
star
25

Scalpel

Strips all testing code from an assembly
C#
27
star
26

AssertMessage

Add 'message' parameter to Assertions. Nunit, Mstest, Xunit is supported.
C#
22
star
27

Publicize

Converts non-public members to public hidden members
C#
12
star
28

UniversalAppSample

C#
7
star
29

.github

1
star