I recently added the log4net package in to my WCF web application. now I have few questions I:
I added the log4net package through the VS 2013 package install and
it added whole package. My guess is that log4net is just the dll and I can add it to my project just by adding the dll?
when I added log4net package a packages.config file has been added to my project with this content:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net45" />
</packages>
what is it? can I remove it?
Can I use the log4net all over my project without defining it in top of each class?
I want to add a extra field to message part. for example I want to log the events like this:
Log.Debug(IP,"This is a DEBUG level message. Typically your most VERBOSE level.");
and the log file show it like this:
72 DEBUG 2015-06-16 08:17:41,713 [10] [(null)] [InsertDate] - from source IP(192.168.1.1) This is a DEBUG level message. Typically your most VERBOSE level.
I also added %line in to conversionPattern but it doesn't work. every time it start from 72. How can this be fixed?
How can log the application stop and start in the wcf application?
You actually asked 4 questions in 1.
First, you need to understand that you have used a NuGet package manager and it is a good practice to include available packages using NuGet but not manually as it automatically places package in a proper place, lets you add this package easily to other projects of solution, provides versioning etc.
Read more about NuGet. For example, here.
Now about your questions:
First: No, log4net package contains not only a log4net.dll file. It also has log4net.xml file. Yes, you can add these two files manually without using NuGet package manager.
Second: It is a list of NuGet packages in your project. You can only remove it if you are not going to use NuGet. However, it is not a good idea - let it be there, it is a single light-weight XML-file.
Third: It's a question of OOP. Create a singleton global logger, for example:
public static class LoggingFactory
{
private static ILog _logger;
private static ILog CreateLogger()
{
// Some log4net initialization
return LogManager.GetLogger("Logger");
}
public static ILog GetLogger()
{
return _logger ?? (_logger = CreateLogger());
}
}
public class AnyClassOfYourWholeSolution
{
LoggingFactory.GetLogger().DebugFormat("{0} {1}", a, b);
}
Fourth: It is a question of OOP again. Create an adapter and use it:
public interface ILogger
{
void Debug(string ip, string message);
void Info(string ip, string message);
void Error(string ip, string message);
}
public class Log4NetLogger : ILogger
{
private ILog _logger;
public Log4NetLogger()
{
// Some log4net initialization
_logger = LogManager.GetLogger("Logger");
}
public void Debug(string ip, string message)
{
// ...
}
public void Info(string ip, string message)
{
// ...
}
public void Error(string ip, string message)
{
// ...
}
}
public static class LoggingFactory
{
private static ILogger _loggerAdapter;
private static Initialize(ILogger adapter)
{
_loggerAdapter = adapter;
}
public static GetLogger()
{
return _logger;
}
}
public class BeginningOfYourProject
{
// Main() or Global.asax or Program.cs etc.
LoggingFactory.Initialize(new Log4NetLogger());
}
public class AnyClassOfYourWholeProject
{
LoggingFactory.GetLogger().Debug(ip, message);
}
Or you can extract ip to the properties of log4net:
// During initialization
log4net.ThreadContext.Properties["ip"] = ip;
// log4net Config
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%n%-5p %d %t IP: %property{ip} %m" />
</layout>
It is all up to your fantasy.
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.
I'm writing an application where logging is part of my actual domain model. It's an automation and batch processing tool where end users will be able to view the logs of a batch processing job in the actual application and not just text log files.
So my domain model includes a LogMessage class:
public sealed class LogMessage
{
public string Message { get; }
public DateTime TimestampUtc { get; }
public LogLevel Level { get; }
}
public enum LogLevel
{
Fatal = 5,
Error = 4,
Warn = 3,
Info = 2,
Debug = 1,
Trace = 0
}
I also have a Result class which has a collection property of LogMessages. Results can be saved to and opened from files with my application by end users.
public class Result
{
public bool Succeeded {get; set;}
public string StatusMessage {get; set;}
public IList<LogMessage> LogMessages {get; set;}
}
My application also supports third party developers extending the application with plug-ins that can also write log messages. So I've defined a generic ILogger interface for the plug-in developers.
public interface ILogger
{
void Debug(string message);
void Error(string message);
void Fatal(string message);
void Info(string message);
void Log(LogLevel level, string message);
void Trace(string message);
void Warn(string message);
}
I provide an instance of an ILogger to the plug-ins which writes to Result.LogMessages.
public interface IPlugIn
{
Output DoSomeThing(Input in, ILogger logger);
}
I obviously also want to be able to log from my own internal code and ultimately want Result.LogMessages to contain a mixture of my internal log messages and log messages from plug-ins. So an end user having trouble could send me a Result file that would contain debug logs both from my internal code, and any plug-ins used.
Currently, I have a solution working using a custom NLog target.
public class LogResultTarget : NLog.Targets.Target
{
public static Result CurrentTargetResult { get; set; }
protected override void Write(NLog.LogEventInfo logEvent)
{
if (CurrentTargetResult != null)
{
//Convert NLog logEvent to LogMessage
LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name);
LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message);
CurrentTargetResult.LogMessages.Add(lm);
}
}
protected override void Write(NLog.Common.AsyncLogEventInfo logEvent)
{
Write(logEvent.LogEvent);
}
}
This class forwards message to the Result assigned to the static LogResultTarget.CurrentTargetResult property. My internal code logs to NLog loggers, and and I have a implementation of ILogger that logs to an NLog.Logger as well.
This is working, but feels really fragile. If CurrentTargetResult is not set correctly or not set back to null I can end up with log messages being stored to results that they do not apply to. Also because there is only one static CurrentTargetResult there's no way I could support processing multiple results simultaneously.
Is there a different/better way I could approach this? Or is what I'm trying to do fundamentally wrong?
I think your approach is the right one, but you could save effort by using a library which already does this abstraction for you. The Common Logging library is what you're after.
Your domain code will depend only on the ILogger interface from Common Logging. Only when your domain is used by a runtime e.g. Web API, do you then configure what logging provider you're going to use.
There are a number of pre-built providers available as separate nuget packages:
Common.Logging provides adapters that support all of the following popular logging targets/frameworks in .NET:
Log4Net (v1.2.9 - v1.2.15)
NLog (v1.0 - v4.4.1)
SeriLog (v1.5.14)
Microsoft Enterprise Library Logging Application Block (v3.1 - v6.0)
Microsoft AppInsights (2.4.0)
Microsoft Event Tracing for Windows (ETW)
Log to STDOUT
Log to DEBUG OUT
I've used this for a number of years and it's been great to have your domain/library code be reused in another context, but not have to have a fixed dependency on a logging framework (I've moved from Enterprise Libraries to log4net, to finally NLog ... it was a breeze).
In think the static CurrentTargetResult is indeed a bit fragile, but the overal approach is fine.
I propose the following changes:
unstatic the CurrentTargetResult, and always make it initialized,
Something like this:
public class LogResultTarget : NLog.Targets.Target
{
public Result CurrentTargetResult { get; } = new Result();
protected override void Write(NLog.LogEventInfo logEvent)
{
//Convert NLog logEvent to LogMessage
LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name);
LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message);
CurrentTargetResult.LogMessages.Add(lm);
}
protected override void Write(NLog.Common.AsyncLogEventInfo logEvent)
{
Write(logEvent.LogEvent);
}
}
Always initialize the LogMessages:
public class Result
{
public bool Succeeded {get; set;}
public string StatusMessage {get; set;}
public IList<LogMessage> LogMessages {get; set;} = new List<LogMessage>();
}
Retrieve the messages - when needed - with the following calls:
// find target by name
var logResultTarget1 = LogManager.Configuration.FindTargetByName<LogResultTarget>("target1");
var results = logResultTarget1.CurrentTargetResult;
// or multiple targets
var logResultTargets = LogManager.Configuration.AllTargets.OfType<LogResultTarget>();
var allResults = logResultTargets.Select(t => t.CurrentTargetResult);
PS: You could also overwrite InitializeTarget in LogResultTarget for initialing the target
I know that Service Locator pattern is out of favour. When you have something like Global.Kernel.Get<IService>() in your code the consultant will detect a code smell. So where possible, we should be using constructor/property or method injection.
Now let's consider a common task of logging with Ninject. There is a couple of extensions such as ninject.extensions.logging and Ninject.Extensions.Logging.Log4net
that "just work". You reference them in your project and then whenever you inject ILogger you get a wrapped instance of log4net logger that is named after the type you are injecting into. You don't even have a dependency on log4net anywhere in you code, just in the place where you provide it with the configuration. Great!
But what if you need a logger in a class that is not created in a DI container? How do you get it there? It can't be auto injected because the class is created outside of the DI, you can do Global.Kernel.Inject(myObject) because it would be wrong to depend on the DI not in the composition root as explained in the article linked above, so what options do you really have?
Below is my attempt at it, but I do not like it because it feels mighty awkward. I'm actually injecting ILoggerFactory instead of ILogger to the class that creates outside-of-DI object and then pass the ILoggerFactory to the outside-of-DI object in the constructor.
Another way of achieving the same would be to introduce the log4net dependency into the outside-of-DI class, but that also feels backward.
How would you solve this?
And here is a code example:
using System.Threading;
using Ninject;
using Ninject.Extensions.Logging;
namespace NjTest
{
// This is some application configuration information
class Config
{
public string Setting1 { get; set; }
}
// This represent messages the application receives
class Message
{
public string Param1 { get; set; }
public string Param2 { get; set; }
public string Param3 { get; set; }
}
// This class represents a worker that collects and process information
// in our example this information comes partially from the message and partially
// driven by configuration
class UnitCollector
{
private readonly ILogger _logger;
// This field is not present in the actual class it's
// here just to give some exit condition to the ProcessUnit method
private int _defunct;
public UnitCollector(string param1, string param2, ILoggerFactory factory)
{
_logger = factory.GetCurrentClassLogger();
// param1 and param2 are used during processing but for simplicity we are
// not showing this
_logger.Debug("Creating UnitCollector {0},{1}", param1, param2);
}
public bool ProcessUnit(string data)
{
_logger.Debug("ProcessUnit {0}",_defunct);
// In reality the result depends on the prior messages content
// For simplicity we have a simple counter here
return _defunct++ < 10;
}
}
// This is the main application service
class Service
{
private readonly ILogger _logger;
private readonly Config _config;
// This is here only so that it can be passed to UnitCollector
// and this is the crux of my question. Having both
// _logger and _loggerFactory seems redundant
private readonly ILoggerFactory _loggerFactory;
public Service(ILogger logger, ILoggerFactory loggerFactory, Config config)
{
_logger = logger;
_loggerFactory = loggerFactory;
_config = config;
}
//Main processing loop
public void DoStuff()
{
_logger.Debug("Hello World!");
UnitCollector currentCollector = null;
bool lastResult = false;
while (true)
{
Message message = ReceiveMessage();
if (!lastResult)
{
// UnitCollector is not injected because it's created and destroyed unknown number of times
// based on the message content. Still it needs a logger. I'm here passing the logger factory
// so that it can obtain a logger but it does not feel clean
// Another way would be to do kernel.Get<ILogger>() inside the collector
// but that would be wrong because kernel should be only used at composition root
// as per http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
currentCollector = new UnitCollector(message.Param2, _config.Setting1,_loggerFactory);
}
lastResult = currentCollector.ProcessUnit(message.Param1);
//message.Param3 is also used for something else
}
}
private Message ReceiveMessage()
{
_logger.Debug("Waiting for a message");
// This represents receiving a message from somewhere
Thread.Sleep(1000);
return new Message {Param1 = "a",Param2 = "b",Param3 = "c"};
}
}
class Program
{
static void Main()
{
log4net.Config.XmlConfigurator.Configure();
IKernel kernel = new StandardKernel();
kernel.Bind<Config>().ToMethod(ctx => new Config {Setting1 = "hey"});
Service service = kernel.Get<Service>();
service.DoStuff();
}
}
}
App.Config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.13.0" newVersion="1.2.13.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
You should have a look at ninject.extensions.factory. You could either create an interface factory with corresponding binding:
internal interface IUnitCollectorFactory
{
IUnitCollector Create(Param2Type param2);
}
Bind<IUnitCollectorFactory>().ToFactory();
or inject and use a Func-Factory Func<Param2Type>.
This way the IUnitCollectorFactory will be instantiated by the IoC and you can also inject the Config into it instead of passing a parameter.
An alternative would be to write your own factory which is doing the new()-ing, but i personally think the untestable code is often not really worth it. You're writing component- or specification-tests, aren't you? :)
However, that's what Mark Seeman is basically recommending (see Can't combine Factory / DI) - which you had seen if you'd read all the comments of the blog post.
The advantage of the abstract-factory is that it gets you closer to a true composition-root where you're instantiating as much as possible "on application startup" instead of deferring the instantiation of parts of the application to some later time.
The drawback of late-instantiation is that starting the program will not tell you whether the whole object tree can be instantiated - there may be binding issues which only occur later.
Another alternative would be to adjust your design so that IUnitCollector is stateless - i.E. does not depend on getting Param2 as ctor parameter but rather as method parameter. That may, however, have other, more significant, drawbacks so you may choose to keep your design as is.
To apply the use of Func-Factory to your example change the beginning of Service class like this:
private readonly Func<string, string, UnitCollector> _unitCollertorFactory;
public Service(ILogger logger, Config config, Func<string, string, UnitCollector> unitCollertorFactory)
{
_logger = logger;
_config = config;
_unitCollertorFactory = unitCollertorFactory;
}
Note that you no longer need the _loggerFactory field and the ILoggerFactory constructor parameter. Then, instead of newing up UnitCollector, instantiate it like this:
currentCollector = _unitCollertorFactory(message.Param2, _config.Setting1);
Now you can substitute ILoggerFactory in the UnitCollector constructor to ILogger and it will get happily injected.
And don't forget that for this to work you need to reference ninject.extensions.factory.
Your specific case is explained clearly here in the Ninject contextual binding documentation. You can use the following registration:
Bind<ILog>().ToMethod(context => LogFactory.CreateLog(context.Request.Target.Type));
This will inject an ILog abstraction based on the type it is injected into, which is precisely what you seem to need.
This allows you to keep the calling of the factory inside your Composition Root and keeps your code free from any factories or the evil Service Location anti-pattern.
UPDATE
After taking a closer look at all your code I now see the root design 'flaw' that causing these problems. The root problem is that in the constructor of your UnitCollector you are mixing runtime data with dependencies and configuration data this is a bad thing and explained here, here and here.
If you move the runtime parameter param1 out of the constructor into the method, you completely solve the problem, simplify your configuration and allow your configuration to be verified at startup. This will look as follows:
kernel.Bind<UnitCollector>().ToMethod(c => new UnitCollector(
c.Get<Config>().Setting1,
LogFactory.CreateLog(typeof(UnitCollector))));
class UnitCollector {
private readonly string _param2;
private readonly ILogger _logger;
public UnitCollector(string param2, ILogger logger) {
_param2 = param2;
_logger = logger;
}
public bool ProcessUnit(string data, string param1) {
// Perhaps even better to extract both parameters into a
// Parameter Object: https://bit.ly/1AvQ6Yh
}
}
class Service {
private readonly ILogger _logger;
private readonly Func<UnitCollector> _collectorFactory;
public Service(ILogger logger, Func<UnitCollector> collectorFactory) {
_logger = logger;
_collectorFactory = collectorFactory;
}
public void DoStuff() {
UnitCollector currentCollector = null;
bool lastResult = false;
while (true) {
Message message = ReceiveMessage();
if (!lastResult) {
currentCollector = this.collectorFactory.Invoke();
}
lastResult = currentCollector.ProcessUnit(message.Param1, message.Param2);
}
}
}
Do note that in the Service there is now a factory (a Func<T>) for the UnitCollector. Since your whole application keeps looping in that DoStuff method, each loop can be seen as a new request. So on each request you will have to do a new resolve (which you are already doing, but now with the manual creation). Furthermore, you would often need to create some scope around such request to make sure that some instances that are registered with a 'per request' or 'per scope' lifestyle are created just once. In that case you will have to extract that logic out of this class to prevent mixing infrastructure with business logic.
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.
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);
}
}