log4Net dilemma - c#

I have a C# solution containing multiple C# projects. I am planning to add logging in it. This logging should be available in all the projects and preferably use log4Net with rolling file logs.
With the above said premise, I could think of two ways to do that.
Initialize logger in entry point (Program class) of the solution & Get the logger instance & use it as a member variable for every class that needs logging.
Add another project, Utilities & define a Logging class with static logging methods. This class should be initialized in entry point (Program class) of the solution.
What could be the best possible solution?

I have a similar situation. What we've done is use 1 app config for all the projects and use links to reference it.
In the app.config for your app you set the log4net config Section
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
And later set the Appender:
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
...
And in each class you want to log you put a line similar to this:
private static readonly ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Then each class grabs the same logger (singleton).
Would that work for you?

In my projects, I wrote a wrapper around LOG4NET to (theoretically) exchange it with other logging frameworks like NLOG without breaking my code.
My own logging class is accessible through a static singleton. It does initialization in this singleton.
(BTW: I published the source code over at the Code Project some time ago, maybe it gives you some inspiration)

If you're thinking about initializing something at the entry point every single time, then you're writing yourself a case for dependency injection. Using a DI library (like Unity, Castle, or whatever flavor you personally like), you could do either constructor injection or property injection to get your logger class (or Log4Net) initialized automatically.
Additionally, you can use the DI kernel to make it a singleton, so that you only have one instance of it active.

Building off of Queso's answer, we had .dll's that were imported with reflection at runtime. In order to get them to use Log4Net we created a log4net.config file with all the appropriate config sections. We also used the same line of code to intialize the log in each class that Queso references. The seperate config allowed us to use it throughout the entire app domain.
EDIT we also had to make a modifcation to the appsettings file to allow for this.
http://haacked.com/archive/2005/03/07/ConfiguringLog4NetForWebApplications.aspx

Option 1 is the way to go.
If you use static methods you loose the ability to use hierarchical loggers. This feature allows you to configure your log output differently for individual classes or even for entire sub systems e.g. YourNameSpace.Security. This tutorial elaborates on this topic (also the other chapters are a good read).
Building a wrapper is certainly not a bad idea, but it is not strictly necessary. It does allow for a few things though: change log framework, use DI, add additional log levels (e.g. log.Verbose()) or different overloads for logging...
Btw. I would initialize the loggers like this:
ILog log = LogManager.GetLogger(typeof(YourClass));

Related

How dll.config file is used

I have a C# project that outputs a dll: foo.dll. This foo.dll internally uses a legacy library legacy.dll. Here is how my library foo.dll will be used: first I upload these files: foo.dll, legacy.dll, and legacy.dll.config to a third party; then the third party starts up a process which loads my main library foo.dll and executes some functions. When foo.dll is being run, I see exception thrown in legacy.dll saying some configuration "baz" cannot be found. However, I can verify that the configuration "baz" is defined in the legacy.dll.config file. So I think the file legacy.dll.config is not loaded by the process.
So I wonder how config.dll files are used. In my case, considering foo.dll is the only thing within my control, is there a way to load the legacy.dll.config file?
One solution is to place the configuration section that your legacy.dll requires in the configuration (app.config or web.config) for the application that references it.
Do you have control over your legacy application - in other words, are you able to modify it? One occasional challenge is that when all of these extra values are dumped into the configuration it can be more difficult to tell where they are used. They can even get left in the configuration long after you stop using legacy.dll because no one knows what they are and they're afraid to remove them.
Going the other way, if the values are missing from the configuration, it's best not to throw a confusing NullReferenceException or configuration exception that requires someone to dig into the legacy code and figure out why it doesn't work.
There are a few things you can do to make this easier:
One is that your legacy.dll can look for configuration values that are distinct and separate from the rest of your configuration.
That could mean a separate configuration section like
<section name="stuffTheLegacyDllNeeds" type="Legacy.LegacySettingsConfiguration, Legacy" />
<stuffTheLegacyDllNeeds>
....
</stuffTheLegacyDllNeeds>
Or you can disambiguate the appSettings keys with some convention like
<add key="myLegacyLibrary:someSetting" value="true" />
You can also make it easier for other developers by throwing useful exception messages if a required key is missing, like "Legacy requires appSettings key 'xyz' which was not found." They still have to figure out where to find the values but at least they have a clear understanding of the problem.
Also, if you find that Legacy.dll doesn't change settings often or ever, you can code it to replace missing values with default values. That way they can override the defaults if they need to by adding configuration values. But if they don't need the defaults they can just not supply configuration values.
One more approach - this is the one I personally prefer:
Make your legacy class depend on an interface for settings like
public interface ISettings
{
bool SomeSetting { get; }
int SomeOtherSetting { get; }
}
and make your legacy class require an instance of ISettings in its constructor. Now the app that references it "knows" about the settings because it can't use the class without providing it. Your legacy library can provide an implementation like
public class ConfigurationSettings : ISettings
that reads from configuration, and the referencing app could choose to use it. Or it could supply another class that implements the interface. It could also provide a class that contains default values which the referencing app could change. It could also have a set of defaults that it uses internally if the referencing app doesn't provide any values.
Now,
The referencing application knows that your class needs these settings.
The referencing application can choose how to provide them.
Your legacy library no longer depends on the configuration. It depends on an abstraction - ISettings - which can be implemented using configuration or some other way. That means your legacy library will be easier to unit test. Code that explicitly depends on configuration values is much harder to test, or likely never even gets tested.

Design principles to consider when wrapping a 3rd party logger like log4net

I'm creating a logger for a company that has several types of .NET projects (Windows Services, ASP.NET, etc), and I was considering using log4net as my logger, but I don't want to be married to log4net, so I was thinking of wrapping it in my own assembly. I realize some developers don't recommend wrapping log4net because that would be an anti-pattern, but assuming I was going that route anyway, I had some questions:
I am planning to use the design principles mentioned in this article to design my wrapper (using factory method, interfaces, and reflection, I can simply decide which logger I want to use (whether log4net, elmah or something else) by specifying in the config file:
https://www.simple-talk.com/dotnet/.net-framework/designing-c-software-with-interfaces/
Question is:
Should I create this logger project in a separate Visual Studio solution and just use the dll in my client applications? If so, where would the configuration details for log4net go? Would that be supplied by the client application's config file? If so, is that good design? For instance, if I decided to switch away from log4net to a different logging framework, I would not only have to change the config setting to specify the new concrete logger's assembly/class name, but would also have to remove the log4net config entries (and perhaps add the new logger's config entries). Is this considered as an acceptable design approach?
Oh my goodness your timing is awesome. And that article is very relevant to me so thanks! I am doing this very same thing right now. I realized that log4net is a decent logger, but a terrible library for making a logger.
I agree with the article, in that you should not directly expose to log4net. Unless this is a small app it would be too difficult to switch later. And log4net is showing age so that may happen. I like the interface approach overall.
But, wrapping log4net it is a pain in the butt. So in doing my prototype wrapper I feel like I rewrote 50% of log4net, and discarded 25%. Some issues I found:
log4net will grab the "caller information" for you. Normally that is great. But if you wrap log4net, the caller information will point to your logger. So you will have to explicitly grab it yourself. But log4net doesn't provide a way for you to override the caller information. So now you will have to create your own fields for the caller's file, line number, class, and package. Thus, not only do you not gain the benefit here, but it is actually more work than just doing it yourself.
Log4net uses the old pre C#-4.0 method of grabbing the caller information which is slow.
Your will be unable to completely wrap log4net without wrapping the configuration. The caller has to configure the loggers either in code or in their app.config. If they do it in their app.config then they are putting log4net specific stuff in their app, so you failed to hide it with your wrapper. But if you have your wrapper code automatically perform the configuration, you just lost the flexibility of log4net. The third option is to make your own configuration, but then what good did log4net do for you? You just rewrote another piece of it.
You are stuck with the log levels that come with log4net. In our app, we want "categories" instead of "levels" which I then have to map to the log4net "levels" under the hood. Now, all the predefined log4net filters are of no use to me.
Anyone using your wrapper still has to reference log4net in their project anyway.
If your wrapper needs a way to handle errors, or pass them back to the caller, you will have trouble. log4net has its own internal error handling and you will need to hook into that and provide your own. Otherwise, errors (like a misconfigured appender) will just go out to the console. If it was designed as a library for making loggers, it would just throw the exception back up or provide a simple event.
One thing we wanted to get out of log4net is the ability to write to different outputs without us us having to write that code ourselves. Ex: I've never written to the event log, and I think log4net can do that. But it might be easier for me to rip out the Event logging code, rather than to try and wrap that. Same thing with filters.
There are some other problems I had with log4net that aren't directly related to trying to wrap it necessarily.
The code is old. The interfaces don't use generics where they should. Lots of object.
They use the old System.Collections collections. Similar to #1.
It has ifdefs for .NET 1 versus 2, and ifdefs for the obsolete compact framework. ugh.
It is designed to log strings, not structured objects. I made my own code to do so, and so did these people: http://stephenjamescode.blogspot.com/2014/01/logging-custom-objects-and-fields-with.html and http://element533.blogspot.com/2010/05/mapping-message-object-properties-to.html but this feels like basic functionality.
It doesn't support CSV and it is cumbersome to add. http://element533.blogspot.com/2010/05/writing-to-csv-using-log4net.html
It doesn't have any kind of logging "service"
It doesn't provide a way to read or parse the log.
I found it was more effort to configure the appenders than to write your own. Ex: I mapped a bunch of fields to the AdoNetAppender, but it would have taken me less time to just rewrite AdoNetAppender. Debugging a database field mapping in XML is harder than trying to debug the equivalent C# + ADO.NET code. The XML is more verbose and less type safe. This might be specific to me because I had a very structured set of data and a lot of fields.
Sorry for the really long post, I have lots and lots of thoughts on this topic. I don't really dislike log4net, I just think it is out of date and if you are wrapping it, you might be better off writing your own.

Configuring a Log4Net logger using AppSettings

I'm currently switching the logging system of a project away from a homemade logger to Log4Net. Under the previous logger we had created, AppSettings.config contained keys to control whether the app logged to the console, a file, or both. Now, using Log4Net, we will be logging to console, file, and database. Also, I have the configuration for Log4Net set up in an external config, so the files concerned are App.config, Log4Net.config, and AppSettings.config.
My question: Will it be possible to continue using keys from AppSettings to control the combination of these three methods used? I know I can create a set of loggers covering the different combinations, but controlling it directly through AppSettings seems far more efficient, and would be my preferred method of doing things.
Thank you very much for your help and expertise.
You can in your Log4Net.config configure how you want to get the output.
It is possible to get to logging to all three types.
There is not a way to configure log4net to use your AppSettings.config out of the box, but you can write your own Configurator and manually add appenders to your Loggers at runtime as outlined here. It is still some extra overhead to avoid what log4net gives you anyway. What is the benefit of using AppSettings.config as you see it?

IOC for Attributes / Decorators in .NET

Our team has created a library that automates the implementation of exception handling and logging for WCF Services. Using this library, developers need only to decorate a service with a custom attribute, setup some simple configuration file entries, and they can already take advantage of a generic exception handlin​g and logging mechanism.
Here's an example of how the library is used:
[ErrorHandlingBehavior (LogWriterOption.EmailLogWriter, LogWriterOption.SQLLogWriter)]
public class SampleService : ISampleService
{
public string GetData(int value)
{
throw new DivideByZeroException();
//return string.Format("You entered: {0}", value);
}
}
The ErrorHandlingBehavior class makes use of a Logger object that takes in the LogWriterOption enums in the parameters to figure out where to log.
Our original intention was to allow the developer to specify his own logging mechanism and supply it to the ErrorHandlingBehavior, so as to remove the dependency of the solution on the Logger class (instead it takes in any class that implements ILogger). However, specifying attributes in the manner below produces an error:
[ErrorHandlingBehavior (new Logger (new HashSet<LogWriterOptions>
{LogWriterOption.EmailLogWriter, LogWriterOption.SQLLogWriter}))]
It seems we cannot instantiate anything when specifying attributes, and therefore we are now unable to let users specify their own logging mechanism.
Would anyone know of a way around this? How can we feed an instance of a class implementing ILogger to our attribute instead of hard-wiring the depenendency?
I also wrote some logging exception handling behaviors. In situations like this I always asked myself:
What would log4net do?
Your LogWriterOptions appear to translate to log4net appenders. Appenders are generally best done through xml configuration because their requirements change by environment. (Its the logging equivalent of don't put your WCF client binding in code.) In other words, when developing locally: don’t send email and just output to a local text file. When running in QA: output to the DB but don’t send email with a tester breaks something on purpose. In production: do something else completely different. Log4net appenders support all these types of post compile changes (and more).
Back to your question:
In your approach, I would pass the ErrorHandlingBehavior a behavior name as a string like “StandardLogging” which would look up a configurable behavior that results in EmailLogWriter and SQLLogWritter being used.
An alternate approach which is common in logging frameworks is to pass the type of the class being logged. If that type is not explicitly configured, it gets the default appenders.
Note that this configuration approach has the added benefit of
Centralizing logging output options for the entire application. If you change your standards, you don't have to update many class files.
Standardizing what log writer options different pieces of code are using. In the code review meeting you simply ask “Are you using standard logging output?” Check.
Update in response to your "keep it simple" comment:
If keep it simple is the goal, I would say pass nothing to your behavior's constructor. Instead put all the log4net config information in its own log4net.config file and store that as a part of the common logging libraries in source control. Then new projects (or junior devs) are just required to add
<configuration>
<log4net configSource="log4net.config" />
</configuration>
to app.config. The bonus to this approach is that as a part of our build process we defined different log4net.config files for deployment to different environments.
You could use the factory pattern. Have the developer specify a type that will be used to provide an ILogger instance:
[ErrorHandlingBehavior(LoggerFactoryType = "FooBar.MyLoggerFactory")]
This type could implement an interface of yours:
public interface ILoggerFactory
{
ILogger GetLogger();
}
and then inside your custom attribute you could first get the factory type using the Type.GetType method, check if it implements the ILoggerFactory interface, instantiate the factory using the Activator.CreateInstance method and finally call the GetLogger method on that instance.

Multiple Webservices with shared web.config logging to separate flat files with Ent Lib

I'm working on a project where I'm required to use Enterprise Library v.3.1.1.0 Logging block (wish it was log4net or nlog, but I don't get a choice). We have an IIS application where several WCF webservices are hosted, and they share a single web.config file. This is also something I'm not at liberty to change.
None of these services had any logging implemented previously.
So it's pretty straight forward to use the Ent Lib Config utility to set up a rolling flat file listener, and log to a text file the way I want. The problem I'm having is configuring it properly though so each service can have their own separate log file, while they still all share the same web.config.
I've gone through several tutorials, but I'm not understanding how to do this.
Any tips?
The configuration in the web.config is static so there will be one file per configured trace listener. One approach to achieve one file per service is to have each service log to a different category and each category uses a different trace listener. This could even be done programmatically so that services can add their own trace listener's at runtime although this is a bit more advanced than updating the configuration file manually (e.g. ensure service is added only once as well as ensuring that when the configuration is updated it is thread-safe).
Enterprise Library does not have anything analogous to log4net's hierarchical loggers so there is no out of the box way to use reflection to route log messages based on the originating class. It might be possible but you will have to write some code. You could write a Logger facade class to set the category based on the callers namespace. (Also note that there is no namespace wildcard matching like log4net.)

Categories

Resources