Holding a reference to logger in a static class - c#

I am using log4net in which I am obtaining a reference to the logger within a static class like so:
internal static class Constants
{
public static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
}
and using that reference throughout the app like so:
Constants.Log.Info(_message);
However I've got the feeling that this may cause problems and I realise that constant (global?) variables can be a bad thing.
Is there anything wrong with this method?
What are the reasons?
What can be done to resolve these?

You are correct, that is not the best approach, if you want to be able to easily differentiate log levels and use different log targets ("appenders") for each class.
It's usually recommended that each class has a static ILog instance, named after it's fully qualified type:
namespace Animals
{
public class Dog
{
private static readonly ILog Log = LogManager.GetLogger(typeof(Dog));
}
public class Cat
{
private static readonly ILog Log = LogManager.GetLogger(typeof(Cat));
}
}
While this looks like more work compared to having a singleton, it proves very handy in the long term. Once you've done this, it is easy to differentiate Dog and Cat log levels through your configuration file (example shown with log4net syntax):
<log4net>
<!-- appender definitions ommited -->
<!-- default for all loggers in Animals namespace -->
<logger name="Animals">
<level value="WARN"/>
</logger>
<!-- ...but we need full debug for Dogs -->
<logger name="Animals.Dog">
<level value="DEBUG"/>
</logger>
<!-- ...and we want to send Cat messages over e-mail -->
<logger name="Animals.Cat">
<level value="INFO"/>
<appender-ref ref="SmtpAppender"/>
</logger>
</log4net>
Logging frameworks like log4net also use the notion of hierarchical logging: a logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. To make use of this feature, it's best to avoid naming loggers manually (using hardcoded strings), but rather use type information to include the entire namespace.

This essentially creates a singleton, although there are many of problems with that, including making effective unit tests. Unit tests don't need to log and this introduces an unnecessary dependency.
You should consider using an IoC container and dependency injection.

Related

C#: Regarding Nlog settings and usage

i am curious to use nlog. i did not use it previously. so i like to use it now in my project. i have a question.
<rules>
<logger name="SomeNamespace.Component.*" minlevel="Trace" writeTo="logfile" final="true" />
<logger name="*" minlevel="Info" writeTo="logfile" />
</rules>
what does mean here SomeNamespace.Component.* ? show me the usage of this type of rule with sample code
what is minlevel="Info" ? what does mean minlevel here ?
what other option can be set for minlevel ?
thanks
what does mean here SomeNamespace.Component.* ?
It means that this rule will match any loggers which have name starting with SomeNamespace.Component.. Usually name of logger equals name of class where you are creating logger (but you also can provide custom logger name):
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
So rule configuration above will match following logger names:
SomeNamespace.Component.MyClass
SomeNamespace.Component.Cool.Other.Namespace.YourClass
// etc
I.e. any logger created in class from SomeNamespace.Component. namespace will match rule. No matter how many classes with loggers you have there.
what is minlevel="Info" ?
It's a minimal level of log messages which will be logged by logger.
Logger.Debug("This will not be logged");
Logger.Info("This will be logged");
NLog supports following log levels (you can use any of them to control which messages will be logged):
Trace
Debug
Info
Warn
Error
Fatal
Check description of each level at NLog wiki. Usually you should use min level Info which will log any error messages and some high-level details of what system is doing. For debugging purpose you can turn-on Trace or Debug level, but your log file can become huge very quickly. Performance also will hurt when you are writing lot of log messages.

Log4net not working with NUnit tests

So I've done some looking around. Most of the threads I find seem related to people wanting to run log4net in their actual tests. Meaning they want log entries in their test class. I don't want log entries from my actual tests. But I do want the expected log entries from the code I am testing. This is my first time using Log4net. If I run the code on it's own, the log entries work. If I run a test, no log entries though. Im guessing it's not initialized properly or perhaps I don't have log4net setup correctly in my UnitTest (in appconfig or assembly maybe??). This is an MVC 5 application. Here is a basic example.
Nunit Test (basics):
namespace MyUnitTests
[TestFixture]
public class MyTestClass
{
[Test]
public void MyTest
{
//arrange
var testVar = #"string";
//act
MyProjectClass.Method(testVar);
//assert something
}
}
so over in MyProject I have (basics):
public class MyProjectClass : Controller
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(MyProjectClass));
public static void Method(string myString)
{
//does whatever
log("added value");
}
So I've obviously simplified this for the discussion. But when I run my actual test, my test passes, the values and the outcome are all as they should be. I just never see the log entry in the log for method I just tested. Im sure I'm missing something simple. Any help would be greatly appreciated. Thanks!
If you look in the app.config for your main project, you should see a log4net config section that specifies the location of the log file.
You first need to add log4net to your <configSections> like this:
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net, Version=1.2.10.0,
Culture=neutral, PublicKeyToken=1b44e1d426115821" />
</configSections>
You can then add a <log4net> section to the <configuration> tag. Documentation for this can be found here.
I just want to add the above answer. In addition to setting the appconfig in your UnitTest the same as your Application webconfig (editing the value for log file location if you want) you also need to add the assembly entry as you would have for your application already (changing web.config to app.config in my case).
//logger
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "app.config", Watch = true)]
You'll also need to initialize the logger (at least I did) in your unit test. So basically I added this in my arrange of my test if wanted my logging in the method I was testing to fire. Add something like this in your arrange or setup for you test class possibly:
[Test]
public void MyTest
{
//arrange
log4net.ILog log = log4net.LogManager.GetLogger(typeof(MyTestClass));
log4net.Config.XmlConfigurator.Configure();
}

Adding method name in NLog

I am using NLog and following the recommended pattern of having a log declare on each class, for the purpose of being able to track which class/method has written to the log. I do find this very useful to have a bit of a top level 'stack trace' with each log write.
My code used to look like this:
class SomeClass {
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
void DoStuff() {
logger.Debug("stuff"); }
}
I recently had the requirement my single project to write to 3 separate log files, and to do this, I added multiple loggers and targets as specified here: https://stackoverflow.com/a/21711838/191206
However, now in my log files, I have lost the class level name. It now just writes the log name that I specified in the NLog.config. I've considered simply adding the method name myself with a call to
System.Reflection.MethodBase.GetCurrentMethod(); // use Name property
or using something else in Reflection like this
However, I'm wondering if NLog has something built into this that I'm missing? The Debug() method I only see the ability to write a string, with parameters & optionally formatted..
Is this built into NLog?
There is a built in layout renderer called ${callsite} that you can use to include the call site information (class name, method name and source information) in your log entries:
<targets>
<target
name="task1File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog1.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
<target
name="task2File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog2.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
</targets>

dynamic log4net appender name?

Let's say i have 3 smtp appenders in same log4net file whose names are:
<appender name = "emailDevelopment".. />
<appender name = "emailBeta".. />
<appender name = "emailProduction".. />
Let's say i have 3 different servers(Dev, Beta, Production). Depending upon the server, i want to fire the log. In case of Development server, it would fire log from "emailDevelopment". I have a system variable in each server named "ApplicationEnvironment" whose value is Development, Beta, Production based on the server names. Now is there anyway i can setup root in log4net so that it fires email depending upon the server name.
<root>
<priority value="ALL" />
<appender-ref ref="email<environment name from whose appender should be used>" />
</root>
This doesn't directly answer your question, but another approach is to simply have multiple log4net configuration files and call XmlConfigurator.Configure() on the right one. For example, you might have Logging.Development.Config, Logging.Beta.Config and so on.
Somewhere in code, you determine the "environment" and configure using the file you want.
I've even gone so far as to have multiple config files and pull different parts of them out into a single XML representing the "true" config, and then calling the Configure() method on that. For example, Logging.Appenders.Config which has all the appenders, and takes all of them and combines it with one of your environment-specific config files above; the environment-specific ones simply reference what they need, and the rest are effectively inactive/unreferenced for that environment.
Even after having written the only XSD file for log4net configuration I'm still not aware of an easy way to achieve this.
You might be able to do something like:
log4net.GlobalContext.Properties["host"] = new ClassThatToStringsHost();
class ClassThatToStringsHost
{ public override string ToString() { return "whatever"; } }
Now you can reference this value from the Log format with: "%property{host}"
To perform the filtering you will need to use a filter configuration in the adapter(s):
<appender name="file" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.PropertyFilter">
<Key value="host" />
<StringToMatch value="whatever" />
</filter>
<!-- Anything not accepted by the above should be excluded -->
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
There may even be a built-in property you could leverage and this should work. See also this post: http://geekswithblogs.net/rgupta/archive/2009/03/03/dynamic-log-filenames-with-log4net.aspx
For me, myself, and I... I would approach it another way all together. I would derive my own SMTP appender from the default and in the ActivateOptions() method I'd configure the values according to the environment. This would allow you to use one SMTP appender with consistent rules and yet provide three public properties for each of the email addresses you want to send from. It's not hard, give it a try!

How do you configure and enable log4net for a stand-alone class library assembly?

Background
I am writing a class library assembly in C# .NET 3.5 which is used for integration with other applications including third-party Commercial-Off-The-Shelf (COTS) tools. Therefore, sometimes this class library will be called by applications (EXEs) that I control while other times it will be called by other DLLs or applications that I do not control.
Assumptions
I am using C# 3.0, .NET 3.5 SP1, and Visual Studio 2008 SP1
I am using log4net 1.2.10.0 or greater
Constraints
Any solution must:
Allow for the class library to enable and configure logging via it's own configuration file, if the calling application does not configure log4net.
Allow for the class library to enable and configuring logging via the calling applications configuration, if it specifies log4net information
OR
Allow for the class library to enable and configuring logging using it's own configuration file at all times.
Problem
When my stand-alone class library is called by a DLL or application that I do not control (such as a third-party COTS tool) and which doesn't specify log4net configuration information, my class library is unable to do any of it's logging.
Question
How do you configure and enable log4net for a stand-alone class library assembly so that it will log regardless if the calling application provides log4net configuration?
Solution 1
A solution for the first set of constraints is to basically wrap the log4net.LogManager into your own custom LogManager class like Jacob, Jeroen, and McWafflestix have suggested (see code below).
Unfortunately, the log4net.LogManager class is static and C# doesn't support static inheritance, so you couldn't simply inherit from it and override the GetLogger method.
There aren't too many methods in the log4net.LogManager class however, so this is certainly a possibility.
The other drawback to this solution is that if you have an existing codebase (which I do in my case) you would have to replace all existing calls to log4net.LogManager with your wrapper class. Not a big deal with today's refactoring tools however.
For my project, these drawbacks outweighed the benefits of using a logging configuration supplied by the calling application so, I went with Solution 2.
Code
First, you need a LogManager wrapper class:
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace MyApplication.Logging
{
//// TODO: Implement the additional GetLogger method signatures and log4net.LogManager methods that are not seen below.
public static class LogManagerWrapper
{
private static readonly string LOG_CONFIG_FILE= #"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
// If no loggers have been created, load our own.
if(LogManager.GetCurrentLoggers().Length == 0)
{
LoadConfig();
}
return LogManager.GetLogger(type);
}
private void LoadConfig()
{
//// TODO: Do exception handling for File access issues and supply sane defaults if it's unavailable.
XmlConfigurator.ConfigureAndWatch(new FileInfo(LOG_CONFIG_FILE));
}
}
Then in your classes, instead of:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Use:
private static readonly ILog log = LogManagerWrapper.GetLogger(typeof(MyApp));
Solution 2
For my purposes, I have decided to settle on a solution that meets the second set of constraints. See the code below for my solution.
From the Apache log4net document:
"An assembly may choose to utilize a named logging repository rather than the default repository. This completely separates the logging for the assembly from the rest of the application. This can be very useful to component developers that wish to use log4net for their components but do not want to require that all the applications that use their component are aware of log4net. It also means that their debugging configuration is separated from the applications configuration. The assembly should specify the RepositoryAttribute to set its logging repository."
Code
I placed the following lines in the AssemblyInfo.cs file of my class library:
// Log4Net configuration file location
[assembly: log4net.Config.Repository("CompanyName.IntegrationLibName")]
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "CompanyName.IntegrationLibName.config", Watch = true)]
    
References
LogManagerMembers
Jacob's Answer
Jeroen's Answer
McWafflestix's Answer
log4net Manual - Repositories
log4NET from a class library (dll)
You can probably code something around the XmlConfigurator class:
public static class MyLogManager
{
// for illustration, you should configure this somewhere else...
private static string configFile = #"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
if(log4net.LogManager.GetCurrentLoggers().Length == 0)
{
// load logger config with XmlConfigurator
log4net.Config.XmlConfigurator.Configure(configFile);
}
return LogManager.GetLogger(type);
}
}
Then in your classes, instead of:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Use:
private static readonly ILog log = MyLogManager.GetLogger(typeof(MyApp));
Of course, it would be preferable to make this class a service and dynamically configure it with the IoC container of your choice, but you get the idea?
EDIT: Fixed Count() problem pointed out in comments.
In your code you can check if there are any loggers via
log4net.LogManager.GetCurrentLoggers().Count()
You could then for example use an XmlConfigurator to load a default configuration from a file:
log4net.Config.XmlConfigurator.Configure(configFile)
You could do the initialization in a static or regular constructor.
class Sample
{
private static readonly log4net.ILog LOG;
static Sample()
{
if (log4net.LogManager.GetCurrentLoggers().Count() == 0)
{
loadConfig();
}
LOG = log4net.LogManager.GetLogger(typeof(Sample));
}
private static void loadConfig()
{
/* Load your config file here */
}
public void YourMethod()
{
LOG.Info("Your messages");
}
}
In your standalone class library, have a singleton which loads the log4net configuration file using the log4net.Config.XmlConfigurator.
Specifically, you can define all of your code to use your own custom logging class; this class can just be a simple wrapper of the log4net logging calls, with one addition; make a static member which contains the log information you want to log to; initialize that with a call to the XmlConfigurator in the static constructor for that class. That's all you have to do.
You can find a good description here:
log4net: A quick start guide
As the article describes, to configure it fore each assembly separately, create an XML file for your assembly named AssemblyName.dll.log4net and place the following XML code into it:
<?xml version="1.0" encoding="utf-8"?>
<log4net debug="false">
<appender name="XmlSchemaFileAppender" type="log4net.Appender.FileAppender">
<file value="AppLog.xml" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.XmlLayout" />
</appender>
<root>
<level value="WARN" />
<appender-ref ref="XmlSchemaFileAppender" />
</root>
</log4net>
It further describes, to instantiate a new logger, simply declare it as a variable for the entire class as follows:
public class LogSample
{
private static readonly log4net.ILog Log
= log4net.LogManager.GetLogger(typeof(LogSample));
// Class Methods Go Here...
}
You can then use the private variable Log inside your class like:
Log.Info("Sample message");
Likewise you can use Log.Error("Error occurred while running myMethod", ex) to log errors along with the exception details.
What I found is the following:
Don't forget to call log4net.Config.XmlConfigurator.Configure(); to activate your configuration
If you need to know the path of the file(s) written, here some code how to obtain it from Log4Net
I hope this helps.
This works for me for a shared library
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.Assembly.GetExecutingAssembly().GetType());

Categories

Resources