I'm writing a logging class and I would like to be able to get the name of the class that has the call to Helper.Log(string message).
Is this possible using reflection and c#?
Yes, it is quite easy.
Helper.Log("[" + this.GetType().Name + "]: " + message);
Note that if your logger class is really a wrapper around a logging framework (like log4net or NLog), the logging framework can be configured to get the calling class/method for you. For this to work correctly, you have to wrap the logging framework correctly. For NLog and log4net, correctly wrapping (to preserve call site information) generally involves using the "Log" method (rather than the Error, Warn, Info, etc variants) and passing the "logger type" as the first parameter. The "logger type" is the type of your logger that wraps the logging framework's logger.
Here is one way to wrap NLog (taken from here):
class MyLogger
{
private Logger _logger;
public MyLogger(string name)
{
_logger = LogManager.GetLogger(name);
}
public void WriteMessage(string message)
{
///
/// create log event from the passed message
///
LogEventInfo logEvent = new LogEventInfo(LogLevel.Info, _logger.Name, message);
// Call the Log() method. It is important to pass typeof(MyLogger) as the
// first parameter. If you don't, ${callsite} and other callstack-related
// layout renderers will not work properly.
//
_logger.Log(typeof(MyLogger), logEvent);
}
}
And here is how you could do it with log4net:
class MyLogger
{
private ILog _logger;
public MyLogger(string name)
{
_logger = LogManager.GetLogger(name);
}
public void WriteMessage(string message)
{
// Call the Log() method. It is important to pass typeof(MyLogger) as the
// first parameter. If you don't, ${callsite} and other callstack-related
// formatters will not work properly.
//
_logger.Log(typeof(MyLogger), LogLevel.Info, message);
}
}
Related
I just started learning C#, and am redoing past Java projects. I am trying to use Microsoft.Extensions.Logging, and I want to be able to make it available for all my classes in my console application.
Examples I referred to creates a LoggerFactory in the Main() method:
https://www.tutorialsteacher.com/core/fundamentals-of-logging-in-dotnet-core
https://thecodeblogger.com/2021/05/11/how-to-enable-logging-in-net-console-applications/
How can I make MEL loggers available for all classes similar to how log4net/serilog does it? I did refer to microsoft documentation but I m not very familiar with Dependency Injection in C#.
I could use log4net instead, however I saw a question thread on SO, that suggested it's better to program to an logging abstraction, as you can easily change logging providers later on depending on your needs.
ie:
class MyDomain
{
private static Logger _logger = LogManager.GetCurrentClassLogger();
private void SomeFunc()
{
_logger.Trace("this is a test");
}
}
You can create an interface that defines the methods that you want to use for logging.
eg
public interface ILog
{
void LogInformation(string message);
void LogWarning(string message);
void LogError(string message);
}
Then you can implement this into a class where you are using Microsoft.Extensions.Logging to log.
public class Log : ILog
{
private readonly ILogger _logger;
public Logger(ILogger<Logger> logger)
{
_logger = logger;
}
public void LogInformation(string message)
{
_logger.LogInformation(message);
}
}
Now you can use ILog in all the classes and in the future if you want to change the logging provider you can.
Note: This is a simple implementation. you can make this more dynamic depending on your needs.
In ASP.NET Core the default resolver will resolve Microsoft.Extensions.Logging.ILogger<MyClass> in the controller.
Suppose I create a fresh .NET Standard library that is called from the controller.
How do I pass a Microsoft.Extensions.Logging instance into it?
How do I create a new() instance of my class if ILogger<MyClass2> is required?
Can C# create a manual automapping for ILogger<T>, which I can pass into my library?
There are times when Dependency Injection isn't available - or when you're in a context where you shouldn't be using DI (such as a Unit Test, where you're meant to explicitly define each injected service), ditto some forms of Integration testing.
In those cases - if you don't care about logging (such as when prototyping or experimenting) then use the NullLogger, which is built-in to Microsoft.Extensions.Logging.Abstractions (which is the common NuGet package that all MEL-using projects must reference, so it's guaranteed to be available).
For example, if you have a service implementation that requires a non-null ILogger<T>:
public interface IEncabulatorService
{
void Foo();
}
public class TurboEncabulatorService : IEncabulatorService
{
private readonly ILogger log;
public TurboEncabulatorService( ILogger<TurboEncabulatorService> log )
{
this.log = log ?? throw new ArgumentNullException(nameof(log));
}
public void Foo()
{
this.log.LogInformation( "Foo was invoked." );
}
}
Then you can instantiate this with the NullLogger dummy logger (for example, in a Unit testing project) like so:
using Microsoft.Extensions.Logging; // This namespace must be imported because it uses extension-methods.
using Microsoft.Extensions.Logging.Abstractions;
[TestClass]
public class MyTests
{
[TestMethod]
public void TestEncabulator()
{
ILogger<TurboEncabulatorService> log = NullLoggerFactory.Instance.CreateLogger<TurboEncabulatorService>()
IEncabulatorService service = new TurboEncabulatorService( log );
}
}
If you do care about what is logged, then unfortunately you do need to implement your own ILoggerFactory (and log to some internal buffer), but you do not need to provide your own CreateLogger<T>() method to create strongly-typed ILogger<T> instances, as that's provided for-free in the MEL library as an extension method.
Regarding subclasses
You mentioned in your post "derived" classes - because if you have this...
public class BaseService
{
public BaseService( ILogger<BaseService> log )
{
// ...
}
}
public class DerivedService : BaseService
{
// ...?
}
...you might wonder if a DerivedService is supposed to accept ILogger<BaseService> or ILogger<DerivedService> as surely it needs an ILogger<BaseService> to pass down to BaseService, but then DerivedService would lose its strongly-typed category name.
...but this is not the case! If you look at the definition of ILogger<T> you'll see it's a covariant generic interface because it has <out T> rather than just <T>. This means that any variable, method or constructor that accepts an ILogger<Base> will also accept an ILogger<Derived>!
So you have this and it's perfectly legal:
public class BaseService
{
public BaseService( ILogger<BaseService> log )
{
// ...
}
}
public class DerivedService : BaseService
{
public DerivedService( ILogger<DerivedService> log )
: base( log ) // `ILogger<BaseService>` can accept an `ILogger<DerivedService>`!
{
}
}
And you can instantiate an instance of DerivedService or BaseService with a NullLogger as per my earlier example:
BaseService bs = new BaseService( NullLoggerFactory.Instance.CreateLogger<BaseService>() );
DerivedService ds = new DerivedService ( NullLoggerFactory.Instance.CreateLogger<DerivedService>() );
Factory helper:
If you find yourself needing to make a NullLogger (or your own ILogger implementation) frequently then you can use this factory-helper method:
public static TService CreateServiceWithLogger<TService>( Func<ILogger<TService>,TService> ctor )
{
ILogger<TService> log = NullLoggerFactory.Instance.CreateLogger<TService>();
return ctor( log );
}
C#'s type-inference rules will ensure you won't need to provide an explicit TService generic parameter type argument, like so:
TurboEncabulatorService service = CreateServiceWithNullLogger( log => new TurboEncabulatorService( log ) );
You should use Dependency Injection for this.
Your Controller leverages DI resources by referencing the interface in its constructor. For ILogger<Class> this would look this like:
public class MyAwesomeController : Controller
{
private readonly ILogger _logger;
public MyAwesomeController(ILogger<MyAwesomeController> logger)
{
// logger contains a refference to the DIed ILogger
// We assign it to _logger so we can reference it from other
// methods in the class
_logger = logger;
}
public IActionResult GetIndex()
{
// Log something
//_logger.LogInformation();
return View();
}
}
You can find a lot more detail in the documentation: Logging in ASP.NET Core
Given a class with a constructor signature of
public Foo(ILogger<Foo> logger) {
// ...
}
that I want to test, I need some way to provide an ILogger<Foo> in the test. It's been asked before, but the only answer then was to set up a full-blown service registry, configure logging and resolve the logger from there. This seems very overkill to me.
Is there a simple way to provide an implementation of ILogger<T> for testing purposes?
Note: it doesn't have to actually log anything - just not blow up when the subject under test tries to log something.
Starting from dotnet core 2.0 there's a generic NullLogger<T> class available:
var foo = new Foo(NullLogger<Foo>.Instance);
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger-1?view=aspnetcore-2.1 (docs)
https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/NullLoggerOfT.cs (source)
Or if you need it as part of your services:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nullloggerfactory?view=aspnetcore-2.1 (docs)
You can create an instance of ILogger<Foo> using NullLoggerFactory as the factory.
Consider the following controller:
public abstract class Foo: Controller
{
public Foo(ILogger<Foo> logger) {
Logger = logger;
}
public ILogger Logger { get; private set; }
}
A sample unit test could be:
[TestMethod]
public void FooConstructorUnitTest()
{
// Arrange
ILogger<FooController> logger = new Logger<FooController>(new NullLoggerFactory());
// Act
FooController target = new FooController(logger);
// Assert
Assert.AreSame(logger, target.Logger);
}
If you use generic logger (ILogger<>) in your classes those instances are generated from IServiceProvider you should register generic NullLogger<> on service provider as below. Not important what you use generic type T in ILogger<>
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
You have two options:
Create empty implementation of ILogger<Foo> by hand and pass an instance of it to ctor.
Create same empty implementation on the fly using some mocking framework like Moq, NSubstitute, etc.
You could inject ILoggerFactory instead and then create the logger
public Foo(ILoggerFactory loggerFactory) {
logger = loggerFactory.CreateLogger<Foo>();
// ...
}
At startup you need to add the NullLoggerFactory service of course:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>()
From the docs for ILogger<T> (emphasis mine):
A generic interface for logging where the category name is derived from the specified TCategoryName type name. Generally used to enable activation of a named ILogger from dependency injection.
So one option would be to change the implementation of the Foo method to take a plain ILogger and use the NullLogger implementation.
You should use the Null Object Pattern. This has two advantages for you: 1) you can get your tests up and running quickly and they won't "blow up", and 2) anyone will be able to use your class without supplying a logger. Just use NullLogger.Instance, or NullLoggerFactory.Instance.
However, you should use a mocking framework to verify that log calls get made. Here is some sample code with Moq.
[TestMethod]
public void TestLogError()
{
var recordId = new Guid("0b88ae00-7889-414a-aa26-18f206470001");
_logTest.ProcessWithException(recordId);
_loggerMock.Verify
(
l => l.Log
(
//Check the severity level
LogLevel.Error,
//This may or may not be relevant to your scenario
It.IsAny<EventId>(),
//This is the magical Moq code that exposes internal log processing from the extension methods
It.Is<It.IsAnyType>((state, t) =>
//This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
//Note: messages should be retrieved from a service that will probably store the strings in a resource file
CheckValue(state, LogTest.ErrorMessage, "{OriginalFormat}") &&
//This confirms that an argument with a key of "recordId" was sent with the correct value
//In Application Insights, this will turn up in Custom Dimensions
CheckValue(state, recordId, nameof(recordId))
),
//Confirm the exception type
It.IsAny<NotImplementedException>(),
//Accept any valid Func here. The Func is specified by the extension methods
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
//Make sure the message was logged the correct number of times
Times.Exactly(1)
);
}
private static bool CheckValue(object state, object expectedValue, string key)
{
var keyValuePairList = (IReadOnlyList<KeyValuePair<string, object>>)state;
var actualValue = keyValuePairList.First(kvp => string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0).Value;
return expectedValue.Equals(actualValue);
}
For more context, see this article.
If you need to verify the calls in addition to just provide the instance, it gets somewhat complicated. The reason is that most calls does not actually belong to the ILogger interface itself.
I have written a more detailed answer here.
Here is a small overview.
Example of a method that I have made to work with NSubstitute:
public static class LoggerTestingExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log(
LogLevel.Error,
0,
Arg.Is<FormattedLogValues>(v => v.ToString() == message),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
}
}
And this is how it can be used:
_logger.Received(1).LogError("Something bad happened");
You should try this for mocking ILogger:
mock.Setup(m => m.Log<object>(It.IsAny<LogLevel>(),It.IsAny<EventId>(),It.IsAny<object>(),It.IsAny<Exception>(),It.IsAny<Func<object, Exception,string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, obj, exception, func) =>
{
string msg = func.Invoke(obj, exception);
Console.WriteLine(msg);
});
This worked for me:
private FooController _fooController;
private Mock<ILogger<FooController>> _logger;
[TestInitialize]
public void Setup()
{
_logger = new Mock<ILogger<FooController>>();
_fooController = new FooController(_logger.Object);
}
EDIT 4: "From" seems to be a reserved word in NLog. Changing it "FromID" worked. this is an awesome way to pass variables to NLog and still keep your code clean !!!! THANK MIKE!!!
EDIT 3. I really like this idea.:
Implemented a helper class as Mike suggested below:
public class NLogHelper
{
//
// Class Properties
//
private Logger m_logger;
private Dictionary<string, object> m_properties;
//
// Constructor
//
public NLogHelper(Logger logger)
{
m_logger = logger;
m_properties = new Dictionary<string, object>();
}
//
// Setting Logger properties per instancce
//
public void Set(string key, object value)
{
m_properties.Add(key, value);
}
//
// Loggers
//
public void Debug(string format, params object[] args)
{
m_logger.Debug()
.Message(format, args)
.Properties(m_properties)
.Write();
}
and in my main code, I have:
private NLogHelper m_logger;
public void Start()
{
m_logger = new NLogHelper(LogManager.GetCurrentClassLogger());
m_logger.Set("From", "QRT123"); // Class setting.
m_logger.Debug("Hello ");
}
And the target set in the config file as follows:
<target xsi:type="File"
name ="LogFile" fileName="C:\QRT\Logs\QRTLog-${shortdate}.log"
layout ="${date}|${level}|${event-properties:item=From}|${message} "/>
But the output has a BLANK in the place of the 'from' property ???
So I'm ALMOST THERE... but it does not seem to work??
EDIT 2:
I am now trying to create my own version of the NLog call:
private void Log_Debug (string Message)
{
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "What is this?", Message);
theEvent.Properties["EmployeeID"] = m_employeeID;
m_logger.Log(theEvent);
}
The issue is that I have to format the string for the calls (but a huge performance deal)... but this seems like a hack??
Ideally, I would declare properties in the custom layout renderer and instead of setting those properties in the configuration file, each instance of my class would have the property set... something like [ID = m_ID] for the whole class. This way whenever a NLog is called from that class, the ID property is set and NLog's custom layout renderer can use this property to output it. Am I making sense??
I'm new to NLog and have been looking at custom renderers.
Basically, my goal is to have my log statements be:
_logger.Debug ("My Name is {0}", "Ed", ID=87);
and I'd like my rendered to be something like:
layout = ${ID} ${date} ${Level} ${Message}
That's it. ${ID} can have a default value of 0. fine. But ideally, I'd like every call to have the ability to specify an ID without needing to have 3 lines everytime I want to log.
I've seen custom renderers allowing me to customize what I output but i'm not sure how I can customize the properties I pass to it without
https://github.com/NLog/NLog/wiki/Extending%20NLog shows how I can add properties but I don't know how to call them.
Also, https://github.com/NLog/NLog/wiki/Event-Context-Layout-Renderer shows how I can set custom properties but that involved the creation of a LogEventInfo object every time I want to log something.
Nlog Custom layoutrenderer shows how to customize the output.. again... not how to customize the inputs.
This is for a Console app in C# targeting .NET 4.0 using VS2013
Thanks
-Ed
Event properties (used to be called event-context) would be the built-in way to do what you want. If you are using NLog 3.2+ you can use the fluent api, which may be a bit more appealing than creating LogEventInfo objects. You can access this api by by using the namespace NLog.Fluent.
Your layout would then be defined like this:
${event-properties:item=ID} ${date} ${Level} ${Message}
Then using the fluent api, log like this:
_logger.Debug()
.Message("My name is {0}", "Ed")
.Property("ID", 87)
.Write();
Other than setting properties per event as above, the only other option would be to set properties per thread using MDC or MDLS.
NLog dosen't have a way (that I have found) of setting per-logger properties. Internally, NLog caches Logger instances by logger name, but does not guarantee that the same instance of Logger will always be returned for a given logger name. So for example if you call LogManager.GetCurrentClassLogger() in the constructor of your class, most of the time you will get back the same instance of Logger for all instances of your class. In which case, you would not be able to have separate values on your logger, per instance of your class.
Perhaps you could create a logging helper class that you can instantiate in your class. The helper class can be initialized with per-instance property values to be logged with every message. The helper class would also provide convenience methods to log messages as above, but with one line of code. Something like this:
// Example of a class that needs to use logging
public class MyClass
{
private LoggerHelper _logger;
public MyClass(int id)
{
_logger = new LoggerHelper(LogManager.GetCurrentClassLogger());
// Per-instance values
_logger.Set("ID", id);
}
public void DoStuff()
{
_logger.Debug("My name is {0}", "Ed");
}
}
// Example of a "stateful" logger
public class LoggerHelper
{
private Logger _logger;
private Dictionary<string, object> _properties;
public LoggerHelper(Logger logger)
{
_logger = logger;
_properties = new Dictionary<string, object>();
}
public void Set(string key, object value)
{
_properties.Add(key, value);
}
public void Debug(string format, params object[] args)
{
_logger.Debug()
.Message(format, args)
.Properties(_properties)
.Write();
}
}
This would work with the same layout as above.
NLog 4.5 supports structured logging using message templates:
logger.Info("Logon by {user} from {ip_address}", "Kenny", "127.0.0.1");
See also https://github.com/NLog/NLog/wiki/How-to-use-structured-logging
See also https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-properties-with-Microsoft-Extension-Logging
Use MDLC Layout Renderer
MappedDiagnosticsLogicalContext.Set("PropertyName", "PropertyValue");
MappedDiagnosticsLogicalContext.Set("PropertyName2",
"AnotherPropertyValue");
In your nlog config:
${mdlc:item=PropertyName} ${mdlc:item=PropertyName2}
https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer
I had 6 variables I wanted to send to structured logging in multiple places (so when I get a user report I can search the log database on our key id fields). I created a logging scope class that leverages the MDLC. So it should be thread safe, work with async/await code and be 3 lines of code for 6 variables everywhere used.
public class MyClassLoggingScope : IDisposable
{
private readonly List<IDisposable> context;
public MyClassLoggingScope(MyClass varSource)
{
this.context = new List<IDisposable>
{
MappedDiagnosticsLogicalContext.SetScoped("Var1", varSource.Var1)
// list all scoped logging context variables
}
}
public void Dispose()
{
foreach (IDisposable disposable in this.context)
{
disposable.Dispose();
}
}
}
Usage:
using (new MyClassLoggingScope(this))
{
// do the things that may cause logging somewhere in the stack
}
Then as flux earlier suggested, in the logging config you can use ${mdlc:item=Var1}
This is propably not the best way to do this and not even thread safe but a quick and dirty solution: You could use Environment variables:
Environment.SetEnvironmentVariable("NLogOptionID", "87");
logger.Debug("My Name id Ed");
Environment.SetEnvironmentVariable("NLogOptionID", null);
In your layout, you can use environment variables.
${environment:variable=NLogOptionID}
I use Ninject as a DI Container in my application. In order to loosely couple to my logging library, I use an interface like this:
public interface ILogger
{
void Debug(string message);
void Debug(string message, Exception exception);
void Debug(Exception exception);
void Info(string message);
...you get the idea
And my implementation looks like this
public class Log4NetLogger : ILogger
{
private ILog _log;
public Log4NetLogger(ILog log)
{
_log = log;
}
public void Debug(string message)
{
_log.Debug(message);
}
... etc etc
A sample class with a logging dependency
public partial class HomeController
{
private ILogger _logger;
public HomeController(ILogger logger)
{
_logger = logger;
}
When instantiating an instance of Log4Net, you should give it the name of the class for which it will be logging. This is proving to be a challenge with Ninject.
The goal is that when instantiating HomeController, Ninject should instantiate ILog with a "name" of "HomeController"
Here is what I have for config
public class LoggingModule : NinjectModule
{
public override void Load()
{
Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
.InSingletonScope();
Bind<ILogger>().To<Log4NetLogger>()
.InSingletonScope();
}
private string GetParentTypeName(IContext context)
{
return context.Request.ParentContext.Request.ParentContext.Request.Service.FullName;
}
}
However the "Name" that is being passed to ILog is not what I'm expecting. I can't figure out any rhyme or reason either, sometimes it's right, most of the time it's not. The Names that I'm seeing are names of OTHER classes which also have dependencies on the ILogger.
I personally have no interest in abstracting away my logger, so my implementation modules reference log4net.dll directly and my constructors request an ILog as desired.
To achieve this, a one line registration using Ninject v3 looks like this at the end of my static void RegisterServices( IKernel kernel ):
kernel.Bind<ILog>().ToMethod( context=>
LogManager.GetLogger( context.Request.Target.Member.ReflectedType ) );
kernel.Get<LogCanary>();
}
class LogCanary
{
public LogCanary(ILog log)
{
log.Debug( "Debug Logging Canary message" );
log.Info( "Logging Canary message" );
}
}
For ease of diagnosing logging issues, I stick the following at the start to get a non-DI driven message too:
public static class NinjectWebCommon
{
public static void Start()
{
LogManager.GetLogger( typeof( NinjectWebCommon ) ).Info( "Start" );
Which yields the following on starting of the app:
<datetime> INFO MeApp.App_Start.NinjectWebCommon - Start
<datetime> DEBUG MeApp.App_Start.NinjectWebCommon+LogCanary - Debug Logging Canary message
<datetime> INFO MeApp.App_Start.NinjectWebCommon+LogCanary - Logging Canary message
The Ninject.Extension.Logging extension already provides all you are implementing yourself. Including support for log4net, NLog and NLog2.
https://github.com/ninject/ninject.extensions.logging
Also you want to use the following as logger type:
context.Request.ParentRequest.ParentRequest.Target.Member.DeclaringType
Otherwise you will get the logger for the service type instead of the implementation type.
The Scope of ILog and ILogger needs to be Transient, otherwise it will just reuse the first logger that it creates. Thanks to #Meryln Morgan-Graham for helping me find that.
Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
.InSingletonScope();
You are currently binding in Singleton scope, so only one logger is created which will use the name of the first one created. Instead use InTransientScope()
maybe my answer is late but I'm using this format:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ILog>()
.ToMethod(c => LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType))
.InSingletonScope();
}
For all of you that are still looking for the correct answer, the correct implementation is :
public class LoggingModule : NinjectModule
{
public override void Load()
{
Bind<ILog>().ToMethod(x => LogManager.GetLogger(x.Request.Target.Member.DeclaringType));
Bind<ILogger>().To<Log4NetLogger>()
.InSingletonScope();
}
}
Emphasis on:
x.Request.Target.Member.DeclaringType
I do like the idea of wrapping the Log4Net in my own interfaces. I don't want to be dependent on Ninjects implementation, because to me that just means I take a dependency on Ninject throughout my application and I thought that was the exact opposite of what dependency injection is for. Decouple from third party services. So I took the original posters code but I changed the following code to make it work.
private string GetParentTypeName(IContext context)
{
var res = context.Request.ParentRequest.ParentRequest.Service.FullName;
return res.ToString();
}
I have to call ParentRequest.ParentRequest so that when I print the layout %logger it will print the class that calls the Log4Net log method instead of the Log4Net class of the method that called the Log method.