Exception capturing and Error logging - c#

I have got a utility class, called ErrorLog, which basically does some basic error logging kinda stuff, recording error messages, stacktraces, etc.
So in my main c# app, I almost always chuck this piece of code, ErrorLog el = new ErrorLog() into the catch(Exception e) part, and then start calling its methods to do logging.
For example, here is 1 of the methods in ErrorLog class
public void logErrorTraceToFile(string fname, string errorMsg, string errorStackTrace)
{
//code here
}
Anyway, I am just wondering if it's a good approach to log errors in this way? It seems to me that this solution is a bit clumsy. (considering in each catch block you create el object and call its method, repeatedly.)
Also, in terms of storing error log files, where it the best / most reasonable location to save them? Currently, I just hard-coded the directory, C:\ErrorLogs\, as I am still testing a few things. But I do want to get it right before I forget.
So any ideas or suggestions?
Thanks.

Look at ELMAH This is very efficient in handling and catching errors in the application.
The errors get logged in the database.

Usually I'm using the Singleton pattern to have one application wide Logger.
public class Logger
{
protected static Logger _logger;
protected Logger()
{
// init
}
public void Log(string message)
{
// log
}
public static Logger GetLogger()
{
return _logger ?? _logger = new Logger();
}
}
As a place to store data I would use the application data or user data directory, only there you can be sure to have write access.
Edit: That's how you would use the Logger from any place in your code:
Logger.GetLogger().Log("test");

Related

using(), access data from parent callee for logging purposes

I have a custom build log framework that logs to a database.
For example it can do
L.e("Error invalid password", userGuid);
This works fine for general use but the application is quite complex and there are a lot of different parts that are called from the main code. For example a login sequence could send an SMS for OTP which is handled by a completely other part of the system and it does not make sense to pass a lot of values thru just for logging purposes.
What I want to achieve is to tag the log with for example userGuid so that I can search for everything related to this specific user. I also want to tag any logging in the SMS module even though the SMS module does not know anything about the user concept.
So what I am thinking of is if it is possible to get the current threadid and store some things regarding the logging in a higher level. I wonder if this is at all possible.
Psuedo code:
void Login(UserName, Password) {
User user = UserManager.GetUser(UserName)
using(L.SetUser(user.ID)) { //Here I want to use user.ID later in code that dont know the context
SmsManager.SendOtp(user.Phonenumber)
}
}
public class SmsManager {
public static void SendOtp(string phonenumber) {
if (phonenumber == "") {
L.error("Phone number is empty"); //How can I use the value from L.SetUser above? Could I use a hash table of threadids in L or would that be a bad idea?
}
}
}
Kind regards
Jens
Can you show us some snippets from L? Is that a static class? Does SetUser set a static variable? You could use the using block the way you suggest. You'd want to implement IDisposable and clear the UserID value in the Dispose method. But if UserID is a static variable, then this solution will not work in a multi-threaded environment (without some other changes). And the design just seems odd to me.
Overall seems like you are using static a lot. That can get you into trouble.
There are lots of possible solutions. Tough to say what's best without seeing some more code. Here is one way using dependency injection to keep your modules separate, as you want.
Define an interface for your logger.
public interface ILogger
{
void Error(string message);
}
Implement with a class that adds the user information:
public class MessageWithUserLogger : ILogger
{
private readonly string _userId;
public MessageWithUserLogger(string userId)
{
_userId = userId;
}
public void Error(string message)
{
L.error(message, _userId);
}
}
Change SmsManager class to be non-static and depend on the ILogger abstraction rather than the L implementation:
public class SmsManager
{
private readonly ILogger _logger;
public SmsManager(ILogger logger)
{
_logger = logger;
}
public void SendOtp(string phonenumber)
{
if (phonenumber == "")
{
_logger.Error("Phone number is empty");
}
}
}
Inject the logger with userID when that information is available:
void Login(UserName, Password)
{
User user = UserManager.GetUser(UserName);
ILogger logger = new MessageWithUserLogger(user.ID);
SmsManager smsManager = new SmsManager(logger);
smsManager.SendOtp(user.Phonenumber);
}
The using statement is not intended to be used like this. The using statement was introduced to be able to define a limited scope, and at the same time make sure objects are disposed using the IDisposable interface (see also https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement).
The way you are using the using statement makes it seems as if the property is sent when you start the scope, and would somehow be "unset" afterwards, but this is not the case.
When working with loggers and starting from your pseudo code, I would say your logging framework should be extended to create a context specific logger when you go into the using, and then pass the logging context to the static function. it would look then as below:
void Login(UserName, Password) {
User user = UserManager.GetUser(UserName)
using(var logContext = L.CreateContext(user.ID)) { //Here I want to use user.ID later in code that dont know the context
SmsManager.SendOtp(logContext, user.Phonenumber)
}
}
public class SmsManager {
public static void SendOtp(LogContext logContext, string phonenumber) {
if (phonenumber == "") {
logContext.error("Phone number is empty"); //How can I use the value from L.SetUser above? Could I use a hash table of threadids in L or would that be a bad idea?
}
}
}
Instead of passing the log context, it is theoretically possible to store the context inside the L object and map it to a thread ID, and, later on in the functions check if there is a specific log context for that thread when you log something. In the IDisposable interface implementation of the LogContext object, you should then remove the context (which corresponds with the end of your using() scope). I would however not do this, because it "hides" a bunch of logic, but even more, it relies on the fact that every function will be executed in the same thread. This in combination with hiding this, makes it a possible source of errors (if the user of the code isn't aware that this is linked to the thread, and changes the thread, you might miss information, make wrong assumptions based on the logging, etc). I think it is not bad practice if you have functions like the SMS manager that has a number of helper functions, to pass in a context specific object.
Also, be aware that this is a concept that exists in most popular logging libraries such as Serilog, and, in almost all cases, writing your own logging libraries isn't the most profitable business (since most of these libraries also have extensions that allow you to write a custom sink, which for example would then write the log output to a database for your specific scenario (but you get all the rest for free).

Serilog Not Outputting Anything

Using .Net 4.6 I have a static Serilog helper class - I've stripped down to the essentials as follows:
public static class SerilogHelper
{
private static ILogger log;
private static ILogger CreateLogger()
{
if (log == null)
{
string levelString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
"BizTalk.Common", "serilog.minimum-level");
SerilogLevel level = (SerilogLevel)Enum.Parse(typeof(SerilogLevel), levelString);
string conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
"BizTalk.Common", "serilog.connection-string");
var levelSwitch = new LoggingLevelSwitch();
levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)level;
log = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.MSSqlServer(connectionString: conString, tableName: "Serilog", autoCreateSqlTable: true)
.WriteTo.RollingFile("log-{Date}.txt")
.CreateLogger();
}
return log;
}
public static void WriteString(string content)
{
var logger = CreateLogger();
logger.Information(content);
}
I have the following unit test:
[TestMethod]
public void UN_TestSerilog1()
{
Common.Components.Helpers.SerilogHelper.WriteString("Simple logging");
}
I've stepped through the debugger to be sure that the "level" variable is being set correctly - it's an enum named "Debug" with value of 1.
Although the Sql Server table is created ok, I don't see any rows inserted or any log txt file.
I've also tried calling logger.Error(content) but still no output.
I've used the same helper code previously on a different site / project and it worked ok.
Where did I go wrong this time?
Serilog.Sinks.MSSqlServer is a "periodic batching" sink and by default, it waits 5 seconds before sending the logs to the database. If your test ends before the sink had a chance to write the messages to the database, they are simply lost...
You need to make sure you dispose the logger before your test runner ends, to force the sink to flush the logs to the database at that point. See Lifecycle of Loggers.
((IDisposable) logger).Dispose();
Of course, if you are sharing a static log instance across multiple tests, you can't just dispose the logger inside of a single test as that would mean the next test that runs won't have a logger to write to... In that case, you should look at your testing framework support for executing code once, before the test suite run starts, and once again, for when the a test suite run ends.
I'm guessing you are using MSTest (because of the TestMethod), so you probably want to look into AssemblyInitialize and AssemblyCleanup, which would give you the opportunity to initialize the logger for all tests, and clean up after all tests finished running...
You might be interested in other ideas for troubleshooting Serilog issues: Serilog MSSQL Sink doesn't write logs to database

Log4Net message construction cost when not debugging

According to this log4net article you should check if debug is enabled prior to any Log.Debug statements to eliminiate the statement construction cost. Is there a better alternative to always having to check if(Log.IsDebugEnabled) prior to any log statements?
Log4Net example:
if (log.IsDebugEnabled)
{
log.Debug("This is entry number: " + i );
}
I don't want to pay the overhead cost of statement construction, but also don't want to check prior to every log statement.
#Grhm and #David have good ideas, but I don't think that David's wrapper is as good as it could be. Wrapping log4net that way. Simply implementing Debug, Info, etc on the wrapper and delegating those down to log4net's Debug, Info, etc methods break log4net's ability to log the call site information. If you wrap this way and tell log4net to log the call site info, log4net will write out the call site in the wrapper, not the call site in your actual code, which is what you want.
I personally don't like using a singleton logger as you lose the ability to tweak logging levels in different parts of your program. If you are working on several components, you might want Info level logging turned on for one component, but only Warn logging (or none at all) for other components. With a singleton logger, all logging in all of your application will be at the same level.
You are denying yourself a lot of log4net's built in (and powerful) capabilities when you wrap log4net incorrectly and when you use a single logger to cover your entire application.
I answered a similar question (about maintaining call site information) here:
how to log method name when using wrapper class with Log4net
To save time, I have included a code example here (uncompiled and untested, but should be close)...
public class MyLog4NetWrapper
{
ILog log;
public MyLog4NetWrapper(string loggerName)
{
log = LogManager.GetLogger(loggerName)
}
public MyLog4NetWrapper(type loggerType)
{
log = LogManager.GetLogger(loggerType)
}
public void Info(string message)
{
if (log.IsInfoEnabled) log.Logger.Log(typeof(MyLog4NetWrapper), LogLevel.Info, message, null);
}
//Defer expensive calculations unless logging is enabled - thanks Grhm for the example
public void Info(Func<string> formattingCallback )
{
if(log.IsInfoEnabled)
{
log.Logger.Log(typeof(MyLog4NetWrapper), LogLevel.Info, formattingCallback(), null);
}
}
//Debug, Warn, Trace, etc are left as an exercise.
}
You can create these loggers in your code like this:
public class MyClass
{
private static readonly ILog log = new MyLoggerWrapper(typeof(MyClass));
public void DoSomething()
{
log.Info("Hello world!");
}
}
The trick to writing a log4net wrapper that preserves the call site information is to use the Log method and to pass the type of your wrapper as the first parameter.
If you are going to write a wrapper in order to implement the functionality that you asked about (deferring execution of any expensive code in the logging call without explicitly checking to see if the desired logging level is enabled), then you might as well put that code in the wrapper rather than implement it as an extension method (which will also suffer from the same loss of call site problem I described above).
Good luck!
The easiest and cleanest way might be the use of the DebugFormat method which actually does the check if the debug level is enabled (see Github-Code of log4net).
but also don't want to check prior to every log statement
When you find yourself repeating the same code over and over, it sounds like a common abstraction may be in order. In this case you can, for example, create a custom wrapper for Log4Net. Something as simple as:
public static class Logger
{
private static ILog _log;
static Logger()
{
log4net.Config.XmlConfigurator.Configure();
_log = log4net.LogManager.GetLogger("Log4Net");
}
public static void Debug(string message)
{
if (_log.IsDebugEnabled)
_log.Debug(message);
}
public static void Info(string message)
{
_log.Info(message);
}
public static void Warn(string message)
{
_log.Warn(message);
}
public static void Error(string message)
{
_log.Error(message);
}
public static void Error(string message, Exception ex)
{
_log.Error(message, ex);
}
public static void Fatal(string message)
{
_log.Fatal(message);
}
public static void Fatal(string message, Exception ex)
{
_log.Fatal(message, ex);
}
}
In this case I made the logger instance static. I'm not 100% sure that will always work as expected. Normally I use this behind a dependency injection framework and configure the logger dependency to be a singleton, handled by the framework. You might instead make this an instance class with instance methods and put it behind a static factory class instead. Test and tweak as necessary.
There are a couple of added benefits here:
Your dependency in Log4Net is isolated to a single class. So if you ever want to use a different logger, you only have to change one class instead of everything in the entire project.
You can easily abstract this behind a dependency injector.
Any other common functionality you want to include in all logging statements can be easily and globally included here.
An example I commonly use for the third benefit might be something like this:
private static string GetLocation()
{
var frame = new StackTrace(1).GetFrame(1);
var method = frame.GetMethod();
return string.Format("{0}:{1}.{2}({3})", Environment.MachineName, method.ReflectedType.FullName, method.Name, frame.GetFileLineNumber().ToString());
}
This gets more meaningful debugging information from the runtime system (though there may be a performance hit, for high-volume systems it's worth testing). So my pass-through error logging function might look like this:
public void Error(string message, Exception ex)
{
_log.Error(string.Format("{0}:{1}", GetLocation(), message), ex);
}
You could use a lambda expression. Like:
log.Debug(() => "This is entry number:" + i);
That way the lambda is only evaluated after the .IsDebugEnabled call.
We have an extension class defined (taken from http://www.beefycode.com/post/Extension-Methods-for-Deferred-Message-Formatting-in-Log4Net.aspx) that has extension methods like:
public static class Log4NetExtensionMethods
{
public static void Debug( this ILog log, Func<string> formattingCallback )
{
if( log.IsDebugEnabled )
{
log.Debug( formattingCallback() );
}
}
// .. other methods as required...
}
I'm not sure if log4net have added lamda type support in more recent releases - but this has been working for me.
If you include the namespace log4net.Util, you are able to call the following extension methods on log4net ILog:
public static void ErrorExt(this ILog logger, Func<object> callback)
This will only call the lambda function when logging error level is enabled. No need to write your own extension methods. It also protects from creating an error while constructing the actual log message by wrapping the creation in a try catch method.
I would look at preprocessor (precompile?) directives.
#if DEBUG
{your logging code here}
#endif
Something like that should do it for you, and then the code only gets compiled in Debug Mode.
You can also use the the Conditional attribute on a method like this:
[System.Diagnostics.Conditional("DEBUG")]
private void YourMethodNameHere(YourMethodSignatureHere)
Take a look at this old question for more information on when/why/how you might use them.
http://stackoverflow.com/questions/3788605/if-debug-vs-conditionaldebug

Why use singleton for application logging?

I was reading about the disadvantages of singleton patterns. A valid use of singleton suggested in many forums is the Logging application. I was wondering why this is a valid use of the pattern. Aren't we maintaing the state information in memory throughout the application?
Why not just use a function:
class Logger
{
public static void Log(string message)
{
//Append to file
}
}
To answer "why not just use a function": this code works incorrectly in multi-thread logging. If two threads try to write the same file, an exception will be thrown. And this is why it's good to use singleton for logging. In this solution, we have a thread safe singleton container, other threads push messages(logs) into the container safely. And the container(always a thread-safe queue) writes the messages/logs into a file/db/etc one by one.
It is better to declare interface:
interface ILogger
{
public void Log(string message);
}
Then implement specific type of logger
class FileLogger : ILogger
{
public void Log(string message)
{
//Append to file
}
}
class EmptyLogger : ILogger
{
public void Log(string message)
{
//Do nothing
}
}
And inject where required. You will inject EmptyLogger in tests. Using singleton will make testing harder, because you'll have to save to file in tests too. If you want to test if class makes correct log entries, you can use mock and define expectations.
About injection:
public class ClassThatUsesLogger
{
private ILogger Logger { get; set; }
public ClassThatUsesLogger(ILogger logger) { Logger = logger }
}
ClassThatUsesLogger takes FileLogger in production code:
classThatUsesLogger = new ClassThatUsesLogger(new FileLogger());
In tests it takes EmptyLogger:
classThatUsesLogger = new ClassThatUsesLogger(new EmptyLogger());
You inject different loggers in different scenarios. There are better ways to handle injections, but you'll have to do some reading.
EDIT
Remember you can still use singleton in your code, as others suggested, but you should hide its usage behind interface to loosen dependency between a class and specific implementation of logging.
I'm not sure what you are referring to when you ask about state information remaining in memory, but one reason to favour singleton over static for logging is that singleton still allows you to both
(1) program to abstractions (ILogger) and
(2) adhere to the dependency inversion principle by practicing dependency injection.
You can't inject your static logging method as a dependency (unless you want to pass something like Action<string> everywhere), but you can pass a singleton object, and you can pass different implementations like NullLogger when writing unit tests.
A singleton logger implementation allows for you to control easily how often your logging is being flushed to disk or the db. If you have multiple instances of the logger then they could all be trying to write at the same time which could cause collisions or performance issues. The singleton allows this to be managed so that you only flush to the store during quiet times and all your messages are kept in order.
In most circumstances the Singleton design pattern is not recommended, because it is a kind of Global State, hides dependencies (making APIs less obvious) and also hard to test.
Logging is not one of those circumstances. This is because logging does not affect the execution of your code. That is, as explained here: http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
your application does not behave any different whether or not a given
logger is enabled. The information here flows one way: From your
application into the logger.
You probably still don't want to use Singleton pattern though. Not quite at least. This is because there's no reason to force a single instance of a logger. What if you wanted to have two log files, or two loggers that behaved differently and were used for different purposes?
So all you really want for logger is to make it easily accessible from everywhere when you need it. Basically, logging is a special circumstances where the best way to go is to have it globally accessible.
The easy way is to simply have a static field in your application that contains the instance of logger:
public final static LOGGER = new Logger();
Or if your logger is created by a Factory:
public final static LOGGER = new LoggerFactory().getLogger("myLogger");
Or if your logger is created by a DI container:
public final static LOGGER = Container.getInstance("myLogger");
You could make your logger implementation be configurable, either through a config file, that you can set to "mode = test" when you are doing testing, so that the logger in those cases can behave accordingly, either not logging, or logging to the console.
public final static LOGGER = new Logger("logConfig.cfg");
You could also make the logger's behavior be configurable at runtime. So when running tests you can simply set it up as such: LOGGER.setMode("test");
Or if you don't make the static final, you can simply replace the static LOGGER with a test logger or mocked logger in the setup of your test.
Something slightly fancier you can do that is close to a Singleton pattern but not quite is:
public class Logger
{
private static Logger default;
public static getDefault()
{
if(default == null)
{
throw new RuntimeException("No default logger was specified.");
}
return default;
}
public static void setDefault(Logger logger)
{
if(default != null)
{
throw new RuntimeException("Default logger already specified.");
}
default = logger;
}
public Logger()
{
}
}
public static void main(String [] args)
{
Logger.setDefault(new Logger());
}
#Test
public void myTest()
{
Logger.setDefault(new MockedLogger());
// ... test stuff
}

How to disable creation of empty log file on app start?

I have configured log4net in my app successfully but one thing is a little bit annoying for me.
The log file is created (empty) after my app start even if no error occurs. I would like to log file be created only after some error.
I actually found a way to do this in this thread:
http://www.l4ndash.com/Log4NetMailArchive/tabid/70/forumid/1/postid/18271/view/topic/Default.aspx
I've tested the first method and it works. Just in case that link is not longer good I'll reproduce the code here. Basically the author states that there are two ways of doing this.
First way:
Create a new locking model that only acquires a lock (and creates the file) if the appropriate threshold for that logger works.
public class MyLock : log4net.Appender.FileAppender.MinimalLock
{
public override Stream AcquireLock()
{
if (CurrentAppender.Threshold == log4net.Core.Level.Off)
return null;
return base.AcquireLock();
}
}
Now in the config file, set the threshold to start out as:
<threshold value="OFF" />
and make sure you set this new LockingModel as you model:
<lockingModel type="Namespace.MyLock" />
I'm using this with a rolling file appender.
The second method is listed at the link. I haven't tried this technique but it seems to be technically sound.
I know this is an old question but I think this can be useful for someone else.
We came across a similar situation where it was required that the application shouldn't leave an empty log file if no errors occurred.
We solved it by creating the following custom LockingModel class:
public class MinimalLockDeleteEmpty : FileAppender.MinimalLock
{
public override void ReleaseLock()
{
base.ReleaseLock();
var logFile = new FileInfo(CurrentAppender.File);
if (logFile.Exists && logFile.Length <= 0)
{
logFile.Delete();
}
}
}
It is derived from the FileAppender.MinimalLock class that will release the lock on the log file after writing each log message.
We added extra functionality that will delete the log file if it is still empty after the lock is released. It prevents the application from leaving empty error log files if the applications runs and exits without any errors.
Pros
It will still create an empty log file during the configuration phase of Log4Net, ensuring that logging is working before the rest of the app starts. However, the log file is deleted immediately.
It doesn't require you to turn off logging in your config file by setting threshold value to "OFF" and than, later on, turn on logging programmatically before writing your first log event.
Cons
This is most likely a slow method of managing your log files because the ReleaseLock method, and the check on the file length, will be called after every log event that is written to the log file. Only use it when you expect to have very few errors and it is a business requirement that the log file shouldn't exist when there are no errors.
The log files are created and deleted when empty. This might be a problem if you have other tools monitoring the log directory for file system changes. However, this was not a problem in our situation.
The following worked for me.The first call to OpenFile() occurs when the logger is configured. Subsequent calls are when actual log message is generated.
class CustomFileAppender : RollingFileAppender
{
private bool isFirstTime = true;
protected override void OpenFile(string fileName, bool append)
{
if (isFirstTime)
{
isFirstTime = false;
return;
}
base.OpenFile(fileName, append);
}
}
And in the config file, change the appender
<log4net>
<appender name="RollingFile" type="<your namespace>.CustomFileAppender">
...
</log4net>
The sequence from log4Net source is as below:
The first call to OpenFile() is because of ActivateOptions() called from FileAppender's constructor.
When log message is generated, AppenderSkeleton's DoAppend() calls PreAppendCheck()
PreAppendCheck() is overridden in TextWriterAppender, the base of FileAppender.
The overridden PreAppendCheck() calls virtual PrepareWriter if the file is not yet open.
PrepareWriter() of FileAppender calls SafeOpenFile() which inturn calls OpenFile()
The problem with that approach is that then if the file exists but is read-only, or is in a directory which doesn't exist etc, you won't find out until another error is already causing problems. You really want to be confident that logging is working before the rest of the app starts.
There may be a way of doing this anyway, but if not I suspect that this is the reason.
Another method that is quite simple is described in this message of the mailing list archive
Basically, with log4net, the log file is created when the logger is configured. To configure it to do otherwise is a bit hacky. The solution is to defer the execution of the configuration. The message above suggests doing the following when setting up the logger:
private static ILog _log = LogManager.GetLogger(typeof(Program));
public static ILog Log
{
get
{
if(!log4net.LogManager.GetRepository().Configured)
log4net.Config.XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
return _log;
}
}
I usually configure log4net with the assembly attribute, which configures the logger automatically (thus creating the log file), and a simple getter for the log:
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
...
public static log4net.ILog Log { get { return _log; } }
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
But removing that and adding in the above getter with the additional logic instead solved the problem for me.
Note: in general I agree that in most cases it would be best to configure the logger and create the file (and even write to it) on application startup.
AcquireLock and ReleaseLock method worked for me, but it bothered me that the file was created/deleted that many times. Here is another similar option that shuts down the logger and deletes the empty logfile when the program completed. Just call RemoveEmptyLogFile when you are done logging.
/// <summary>
/// Sets the logging level for log4net.
/// </summary>
private static void RemoveEmptyLogFile()
{
//Get the logfilename before we shut it down
log4net.Appender.FileAppender rootAppender = (log4net.Appender.FileAppender)((log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository()).Root.Appenders[0];
string filename = rootAppender.File;
//Shut down all of the repositories to release lock on logfile
log4net.Repository.ILoggerRepository[] repositories = log4net.LogManager.GetAllRepositories();
foreach (log4net.Repository.ILoggerRepository repository in repositories)
{
repository.Shutdown();
}
//Delete log file if it's empty
var f = new FileInfo(filename);
if (f.Exists && f.Length <= 0)
{
f.Delete();
}
} // RemoveEmptyLogFile
private static ILog _log = LogManager.GetLogger(typeof(Program));
public static ILog Log
{
get
{
if(!log4net.LogManager.GetRepository().Configured)
log4net.Config.XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
return _log;
}
}

Categories

Resources