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;
}
}
Related
I have a class to whose every instance i create a new logger and attache a buffer appender and a flie appender to it. Everything is being done runtime and no information is picked from the config file.
Now to release resources at the class's custom dispose method i need to shutdown that specific logger and release all of its attached resources so as to avoid any memory leak.
At the moment what i have been doing is atleast flush the file appender and write all logging information but that neither releases the lock on that specific logging file nor does it release any of its resources.
What is the proper way of shutting down the logger while not shutting down other active loggers that are in process
log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
foreach (IAppender iapp in log.Logger.Repository.GetAppenders())
{
BufferingAppenderSkeleton buffered = iapp as BufferingAppenderSkeleton;
if (buffered is BufferingForwardingAppender)
{
((BufferingForwardingAppender)buffered).Flush();
}
}
log.Logger.Repository.Shutdown();
I hope i have made my question clear enough :)
This worked for me:
log.Logger.Repository.Shutdown();
or you can take the long route:
foreach (log4net.Appender.IAppender app in log.Logger.Repository.GetAppenders()) {
app.Close();
}
In this instance, as you are not sharing any appenders, you should be able to use the IAppender.Close() method on all the appenders attached to your logger (this will also cause them all to be flushed).
You should cast the logger to IAppenderAttachable and get the appenders form there; this will allow you to make sure you only call Close() on the top level of your nested appenders. This should cause them to flush and close down their own children in the correct order.
http://logging.apache.org/log4net/release/sdk/html/M_log4net_Appender_IAppender_Close.htm
This will be very dangerous if you are using a standard log4net setup with a configuration!
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
I have custom xml file with the log4net configurations. Below code is used for configuring the log4net. It is working fine.
The question is how does LogManager.Getlogger gets the "MyLogger" when it doesnt know the config file details?
Should we maintain any sequence while calling LogManager.GetLogger and xmlconfigurator.configure ?
Class LoggerClass
{
private static readonly ILog fileLogger = LogManager.GetLogger("MyLogger");
public LoggerClass()
{
FileInfo logConfiguration = new FileInfo("ConfigFile.xml");
//Loading the configuration from the xml file.
XmlConfigurator.Configure(logConfiguration);
}
public void Log(string msg)
{
fileLogger.Log(......);
}
}
LogManager class has static methods that are used by a client to request a logger instance. The GetLogger method is used to retrieve a logger.
The GetLogger method return the object of type ILog which contains methods for logging at different levels and also has properties for determining if those logging levels are enabled in the current configuration.
And about the sequence, the invocation of the Xmlconfigurator.Configure() method sets up the logging functionality, hence before writing any log, the log4net library must be set up using this command Xmlconfigurator.Configure().
And the sequence of calling LogManager.GetLogger() and Xmlconfigurator.Configure() does not matter. Just make sure that before any logging, you have initialized the logger using LogManager.GetLogger() method and have set up the logger using Xmlconfigurator.Configure().
Reference: log4net documentaion
I hope, this helps and answers your question :)
Usually XmlConfigurator.configure() is done at application start in global.asax in ASP.NET application, so that all classes in application get log4net configurations.
//Global.asax
void Application_Start(object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
}
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");
I'm using Visual Studio 2010 to write a simple C#/.NET GUI app, wherein I use a Logger class to write tracing/debugging info to a single file from within all of the various classes of the project. (See source code below.)
Each class's constructor writes an entry to the log when one of its object types is instantiated. One of those classes is a custom GUI controller component (class FileAttributesCtl), which is contained in a couple of the GUI forms used by the program.
The problem I'm having is that two logfiles are created, about 200 msec apart. The first logfile contains (only) a message that a FileAttributesCtl object has been constructed, and the second contains all of the other messages written to the (supposedly) shared logfile output stream. So every time I execute my project code, I get two logfiles.
Stranger still, every time I rebuild my project (F6), a logfile is created for the FileAttributesCtl object, indicating that an control object of this type is actually instantiated during the build process.
This apparently has something to do with threading. If the logfile is not named uniquely (i.e., if I do not append a unique date/time string to the filename), I get an exception, indicating that more than one process (which is actually the VS2010 process itself) is currently using the file.
So my question is: How do I get the singleton object to actually be a single object?
A secondary question is: Why is VS2010 acting this way?
//----------------------------------------
// Logger.cs
class Logger
{
// Singleton object
private static Logger s_logger =
new Logger("C:/Temp/foo.log");
public static Logger Log
{
get { return s_logger; }
}
private TextWriter m_out;
private Logger(string fname)
{
// Add a date/time suffix to the filename
fname = ...;
// Open/create the logging output file
m_out = new StreamWriter(
new FileStream(fname, FileMode.Create, FileAccess.Write,
FileShare.Read));
m_out.WriteLine(DateTime.Now.ToString(
"'$ 'yyyy-MM-dd' 'HH:mm:ss.fff"));
}
...
}
//----------------------------------------
// FileAttributesCtl.cs
public partial class FileAttributesCtl: UserControl
{
private Logger m_log = Logger.Log;
public FileAttributesCtl()
{
m_log.WriteLine("FileAttributesCtl()"); //Written to first logfile
InitializeComponent();
}
...
}
//----------------------------------------
// FileCopyForm.cs
public partial class FileCopyForm: Form
{
private Logger m_log = Logger.Log;
public FileCopyForm()
{
// Setup
m_log.WriteLine("FileCopyForm()"); //Written to second logfile
// Initialize the GUI form
m_log.WriteLine("FileCopyGui.InitializeComponent()");
InitializeComponent();
...
}
...
}
Note: This is very similar to a question from Dec 2009:
Access to singleton object from another thread
but it does not have the answers to my question.
Update
Further investigation shows that the VS2010 is indeed instantiating the custom component during the build, probably so that it can render it in the Designer window.
Also, there are indeed two separate threads calling the Logger constructor (each having a different ManagedThreadID).
Using a static class initializer to construct the singleton object does not work; I still get two logfiles.
Resolution
Upon closer examination, I notice that the custom control is getting instantiated twice, and this is being shown in both logfiles.
Therefore I think the problem is entirely due to the fact that VS instantiates the custom control object prior to executing the program that results in the first logfile being created. The second logfile is then created after the program starts normal execution.
Thus the first logfile is simply a side effect of the build process, and does not really have anything to do with multiple threads executing during normal program operation.
The obvious solution is to remove all logfile side-effect code from the component constructors. Or simply just ignore the first logfile altogether.
It could very well be that Visual Studio is building your UI component (to display in the designer) and in the process, your constructor is getting called which is why you're seeing that log file during the build process.
Static data + threads = trouble
You need to synchronize access to the singleton (and initialization of the singleton).
A static constructor may help
class Logger
{
private static Logger
static Logger()
{
s_logger = new Logger("C:/Temp/foo.log");
}
// ...
or better yet use a logging library (log4net) that handles all this stuff for you.
As for VS builds causing a log to be created, I'm not surprised. It is probably instantiating the forms to discover information about your forms via reflection.
update per comments
#LoadMaster "The static class initializer does not
work. I added more info to the logfile
output to include the current thread's
ManagedThreadID, and sure enough,
there are two different thread IDs
creating the two logfiles."
That's strange. Per MSDN
The static constructor for a class
executes at most once in a given
application domain. The execution of a
static constructor is triggered by the
first of the following events to occur
within an application domain:
An instance of the class is created.
Any of the static members of the class
are referenced.
Your thread must have moved AppDomains or there is some code missing from your snippets.