Using EventLog in ClickOnce application - c#

I've got a library that I use across multiple ClickOnce applications. In the event of an error in this library I would like to write the error to the windows EventLog.
I found a KB article on how but it seems that this requires administrator permissions to search the for the source. Specifically it chokes when trying to search the Security event log.
Is there anyway to work around this and write to the event log in a ClickOnce application? I saw one person trying to write to a known source, but they didn't seem to be able to find a source that was consistently available.
EDIT:
Based on answers here I create an program that's included with my application that I can run on the first run to set up the event source that can get admin privileges. However once the source is created it seems I still cannot write to it.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
if (!EventLog.SourceExists("ATE"))
{
EventLog.CreateEventSource("ATE", "Application");
}
}
Is properly creates a source (which is equivalent to the registry edit provided by Yannick Blondeau). When I write to the source in my non-elevated application I receive an new error, but it still doesn't work. The new error is:
Cannot open log for source 'ATE'. You may not have write access.
EDIT 2:
I've now been trying to get it to work through registry edits on the CustomSD key. I tried adding (A;;0x7;;;AU) to give authenticated users full access but it didn't seem to have any effect.

Unfortunately, the event source requires administrative priveledges to be created. However, you don't need admin rights to write to the event log, or read from it.
There are two ways around this.
You add the new event source when you install the application as an administrator.
You create a simple app that you run as an admin to configure your application. This could even be included in the installer.
If you don't have or want an installer, the load the app onto the computer as an admin and run the program once. Your app startup should configure the event source if it isn't already there anyway, right? (Okay, that's three ways.)
This code snippet is from microsoft: http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlog.aspx and is designed to be run as an admin user.
using System;
using System.Diagnostics;
using System.Threading;
class MySample
{
public static void Main()
{
// Create the source, if it does not already exist.
if (!EventLog.SourceExists("MySource"))
{
//An event log source should not be created and immediately used.
//There is a latency time to enable the source, it should be created
//prior to executing the application that uses the source.
//Execute this sample a second time to use the new source.
EventLog.CreateEventSource("MySource", "MyNewLog");
Console.WriteLine("CreatedEventSource");
Console.WriteLine("Exiting, execute the application a second time to use the source.");
// The source is created. Exit the application to allow it to be registered.
return;
}
// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "MySource";
// Write an informational entry to the event log.
myLog.WriteEntry("Writing to event log.");
}
}
I know it may not be exactly what you were after, but I reckon it's the only way to do this.
EDIT 1: Added some more code
public class EventLogger
{
private const string logName = "Application";
private static string appName = "";
private static bool sourceExists = false;
public static string AppName
{
get { return appName; }
set { appName = value; }
}
public static void Init(string appName)
{
AppName = appName;
sourceExists = EventLog.SourceExists(AppName);
if (!sourceExists)
{
EventLog.CreateEventSource(AppName, logName);
sourceExists = true;
}
}
private static void Write(string entry, EventLogEntryType logType, int eventID)
{
if (sourceExists)
{
EventLog.WriteEntry(AppName, entry, logType, eventID);
}
}
public static void Warning(string entry) { Write(entry, EventLogEntryType.Warning, 200); }
public static void Warning(string entry, int eventID) { Write(entry, EventLogEntryType.Warning, eventID); }
public static void Error(string entry) { Write(entry, EventLogEntryType.Error, 300); }
public static void Error(string entry, int eventID) { Write(entry, EventLogEntryType.Error, eventID); }
public static void Info(string entry) { Write(entry, EventLogEntryType.Information, 100); }
public static void Info(string entry, int eventID) { Write(entry, EventLogEntryType.Information, eventID); }
}
This is the way that I have implemented my EventLogger class which is in use in a production application.
If you could post your code we can do a comparison.
One thing that occurs to me is that when I create my source, I use the application name, but stick with the Application logfile. Are you also attempting to create a new logfile. If so check that it is created in the event viewer.
EDIT 2: Impersonate User with a user token value of zero
This is a bit of a stumper.
Try this code, wrapped around the event writing code.
System.Security.Principal.WindowsImpersonationContext wic = System.Security.Principal.WindowsIdentity.Impersonate(IntPtr.Zero);
// your code to write to event log or any to do something which needs elevated permission--
wic.Undo();
I haven't tried this, simply because my code is working, it comes from here: http://sharenotes.wordpress.com/2008/03/18/cannot-open-log-for-source-you-may-not-have-write-access/

An alternative approach to this is to download the ClickOnce setup.exe file, right click and run as administrator. Not very satisfactory but seems to work.

In the ClickOnce documentation, it is said that if the user running a ClickOnce application is not an administrator, the application will fail writing to the event log.
Full quote:
After a ClickOnce deployment, the application will create an event
source if it does not already exist when the application attempts to
write an event to the Event log. If the user is not an administrator,
the attempt fails and the application will not log any events. In this
case, you can create the event source manually.
To create the event source manually, you will have to add an entry in the registry like this one during your deployment process:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\YourApp]
"EventMessageFile"="%SystemRoot%\\Microsoft.NET\\Framework\\v2.0.50727\\EventLogMessages.dll"
This approach will limit the elevation need at the deployment phase.

Related

How to pass data to injected services

I want to code a small WinForms or WPF program that controls that only one person/computer a time uses a specific shared file through a network share. This file needs to be copied locally when working and copied back to the network share when the work is finished.
I want to create a small WPF application with only one button to flag the file as locked, copy it locally and open the application associated to that file extension. When this application is closed, the file is copied back to the network share and the lock is released. Each computer will use this application to access the file, so it should never be two computers editing the same file at the same time.
The application has to have some kind of configuration file with the path for local and remote folders, the name of the file and the path of the application to open that file. To ease setting up the application it will be done with a SettingsWindow.
I am trying to do it with IoC and some kind of lightweight DI container (i.e. SimpleInjector), but I am having some questions about how to do it properly:
Program.cs
static class Program
{
[STAThread]
static void Main()
{
var container = Bootstrap();
// Any additional other configuration, e.g. of your desired MVVM toolkit.
RunApplication(container);
}
private static Container Bootstrap()
{
// Create the container as usual.
var container = new Container();
// Register your types, for instance:
container.Register<ILauncher, Launcher>();
container.Register<IFileSyncService, FileSyncronizer>();
container.Register<IAppRunnerService, AppRunnerService>();
container.Register<ILockService, LockService>();
// Register your windows and view models:
container.Register<MainWindow>();
container.Register<ConfigurationWindow>();
container.Verify();
return container;
}
private static void RunApplication(Container container)
{
try
{
var app = new App();
var mainWindow = container.GetInstance<MainWindow>();
app.Run(mainWindow);
}
catch (Exception ex)
{
//Log the exception and exit
}
}
}
MainWindow
I modified the constructor to receive an interface ILauncher that will be resolved by the DI container.
public partial class MainWindow : Window
{
public MainWindow(ILauncher launcher)
{
InitializeComponent();
}
…
}
ILauncher interface
public interface ILauncher
{
void Run();
}
Launcher implementation
The launcher implementation will take care of coordinating every task needed to launch the application and edit the file. This consists of: checking and acquiring the lock, synchronizing the file, executing and monitoring that the application has been closed. To follow the Single Responsibility Principle (SRP) this is done through some services injected:
public class Launcher : ILauncher
{
public Launcher(
ILockService lockService,
IAppRunnerService appRunnerService,
IFileSyncService fileSyncService
)
{
LockService = lockService;
AppRunnerService = appRunnerService;
FileSyncService = fileSyncService;
}
public ILockService LockService { get; }
public IAppRunnerService AppRunnerService { get; }
public IFileSyncService FileSyncService { get; }
public void Run()
{
//TODO check lock + adquire lock
//TODO Sinchronize file
//TODO Subscribe to the IAppRunnerService.OnStop event
//TODO Start app through IAppRunnerService.Run method
}
}
My questions:
Creating new instances with the “new” keyword or with manual calls to the container inside classes or windows is a bad practice, so the entry point of the desktop application (usually some kind of a main window) should ask (via the constructor) for everything. It doesn’t seem a proper way when the application grows. Am I right? What is the solution?
In my application, every service needs some kind of runtime data (the executable file path, the local or remote directory…). The container instantiates them early in the application execution, even before this data is known. How can the services receive this data? (Please note that data could be modified later by the SettingsWindow).
Sorry for the extension of this post but I wanted to make my problem and the context clear.
As you've said, you have to ask for some services in the entry point, but what do you mean ask for everything? Your entry point should have only the services it directly uses speciifed as ctor parameters. If your application grows and you suddenly have a dozen services called directly by your entry point - that could indicate that some of the services could be consolidated into a bigger packet. That's opinion-based territory though, as long as you consider it maintainable it's okay.
Many possible solutions here, one would be an ISettingsManager allowing for realtime access to the settings across any services that take it as a dependency.

Windows service Started and stopped automatically, exception handling issue

I have developed a 32 bit service, I am running it in Windows 7 Home Premium x64.
Problem is when I start it, windows gives me the following message
The WLConsumer service on Local Computer started and then stopped. Some services stop
automatically if they are not in use by other services or programs.
I found the following message in the event log
Service cannot be started. System.ArgumentException: Log WLConsumer has already been registered as a source on the local computer.
at System.Diagnostics.EventLogInternal.CreateEventSource(EventSourceCreationData sourceData)
at System.Diagnostics.EventLogInternal.VerifyAndCreateSource(String sourceName, String currentMachineName)
at System.Diagnostics.EventLogInternal.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type)
at WeblogicConsumerService.WeblogicConsumer.winEventlogMe(String logTxt, String logSrc, Char entryType) in C:\Program Files (x86)\CSI\WeblogicConsumerService\WeblogicConsumer.cs:line 136
at WeblogicConsumerService.WeblogicConsumer.OnStart(String[] args) in C:\Program Files (x86)\CSI\WeblogicConsumerService\WeblogicConsumer.cs:line 63
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)
This is my codeblock in the OnStart() method
protected override void OnStart(string[] args)
{
#region WEBLOGIC CREDENTIALS
try
{
//Weblogic URL
this.url = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("URL").ToString();
//Queue name
this.qName = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("Queue").ToString();
//Weblogic login name
this.user = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("User").ToString();
//Weblogic password
this.pwd = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("Pwd").ToString();
//Weblogic Connection Factory
this.cfName = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("ConnectionFactory").ToString();
//root folder
this.rFolder = Registry.LocalMachine.OpenSubKey(#"Software\CSI_WL").GetValue("root").ToString();
}
catch (Exception e)
{
winEventlogMe(e.Message, "WLRegistryKeys", 'e');
}
#endregion
winEventlogMe("Successful start", "SeriviceStartup", 'i');
synchro.Enabled = true;
}
winEventLogMe is the method I am calling for logging.
public static void winEventlogMe(string logTxt, string logSrc, char entryType)
{
#region Log
//Log to event log
EventLog theEvent = new EventLog("WLConsumer");
theEvent.Source = logSrc;
if (entryType == 'e')
theEvent.WriteEntry(logTxt, EventLogEntryType.Error);
else if (entryType == 'i')
theEvent.WriteEntry(logTxt, EventLogEntryType.Information);
else if (entryType == 'w')
theEvent.WriteEntry(logTxt, EventLogEntryType.Warning);
else
theEvent.WriteEntry(logTxt, EventLogEntryType.Error);*/
#endregion
}
When I comment out the calls to winEventLogMe() method in the OnStart() method, the service starts without an error. So obviously something is wrong with the winEventLogMe() method.
Can someone please help me figure out whats the problem as I am totally clueless on how to solve this issue now.
thanx in advance :)
#nick_w I have edited my code as you suggested, the service started succesfully. But on Stopping it I got the following message:
Failed to stop service. System.ArgumentException: The source 'WLConsumer2012' is not registered in log 'ServiceStop'. (It is registered in log 'SeriviceStartup'.) " The Source and Log properties must be matched, or you may set Log to the empty string, and it will automatically be matched to the Source property.
at System.Diagnostics.EventLogInternal.VerifyAndCreateSource(String sourceName, String currentMachineName)
at System.Diagnostics.EventLogInternal.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
at System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type)
at WeblogicConsumerService.WeblogicConsumer.winEventlogMe(String logTxt, String logSrc, Char entryType) in C:\Program Files (x86)\CSI\WeblogicConsumerService\WeblogicConsumer.cs:line 139
at WeblogicConsumerService.WeblogicConsumer.OnStop() in C:\Program Files (x86)\CSI\WeblogicConsumerService\WeblogicConsumer.cs:line 70
at System.ServiceProcess.ServiceBase.DeferredStop()
here is the OnStop() method
protected override void OnStop()
{
winEventlogMe("Successful stop", "ServiceStop", 'i');
}
These event logs are starting to confuse me a lot. I have done the same method of logging in other services and never encountered such problems. How can I be getting these errors in this service yet its not much different from all the others I have done :(
I think this is your problem:
EventLog theEvent = new EventLog("WLConsumer");
Judging by the exception, I am thinking that WLConsumer is the name of the event source. What this means is that you might be better off with this:
EventLog theEvent = new EventLog(logSrc);
theEvent.Source = "WLConsumer";
This is just using the parameters the other way around.
If I do a little decompilation, there is a check like this:
if (!EventLogInternal.SourceExists(logName, machineName, true))
In your case I would think this check is returning true, meaning that it is trying to create a log named WLConsumer but failing because WLConsumer has been registered as an event source.
Edit:
When I have used the event log in the past, I wrote everything to the same combination of source and log. In your case you seem to be using a different combination of source and log each time you write an entry.
From MSDN (emphasis mine):
If you write to an event log, you must specify or create an event Source. You must have administrative rights on the computer to create a new event source. The Source registers your application with the event log as a valid source of entries. You can only use the Source to write to one log at a time. The Source can be any random string, but the name must be distinct from other sources on the computer. It is common for the source to be the name of the application or another identifying string. An attempt to create a duplicated Source value throws an exception. However, a single event log can be associated with multiple sources.
What I would suggest is this:
Use WLConsumer (or WLConsumer2012) as your source, and either
Define your own log, 'WLConsumerServiceEventLog` or something; or
Leave the log blank. They go into the Application log in this case.
Regardless, standard practice seems to be to do something like this prior to running your service for the first time, such as in an installer (copied straight from above link):
// Create the source, if it does not already exist.
if(!EventLog.SourceExists("MySource"))
{
//An event log source should not be created and immediately used.
//There is a latency time to enable the source, it should be created
//prior to executing the application that uses the source.
//Execute this sample a second time to use the new source.
EventLog.CreateEventSource("MySource", "MyNewLog");
Console.WriteLine("CreatedEventSource");
Console.WriteLine("Exiting, execute the application a second time to use the source.");
// The source is created. Exit the application to allow it to be registered.
return;
}
Note the point in the comments re latency. The logs are not necessarily created immediately, so it pays to code with this in mind. You could also use the EventLogInstaller to create the log. This may be the easier option if you are using an installer to deploy your service.
It is critical not to overload the on start method and in order to prevent service start failures, normally the onstart method launches the main code as a separate thread

Windows service on Local Computer started and then stopped error

Usually, I get this error:
(The "service name" service on Local Computer started and then stopped. Some services stop automatically if they are not in use by other service or programs) when there's something wrong with my code, like non-existing drive paths, etc. The windows service will not start.
I have a windows service that backs up folder/files, to a location if it reached the size limit. Details are all provide by an XML Configuration that the windows service reads on start. I have a separate windows forms that has a button that does exactly what my windows service's onstart is doing. I use my windows forms for debugging the code before I put it in my windows service.
When I start my windows forms. It does what it suppose to do. When I put my code in the windows service OnStart() method the error showed up.
Here's my code:
protected override void OnStart(string[] args)
{
private static string backupConfig = #"D:\LogBackupConfig\backupconfig.xml";
private static string serviceStat = #"D:\LogBackupConfig\Status.txt";
private static string fileFolderStat = #"D:\LogBackupConfig\FileFolderStat.txt";
protected override void OnStart(string[] args)
{
if (File.Exists(backupConfig))
{
FileSystemWatcher watcher = new FileSystemWatcher();
XmlTextReader reader = new XmlTextReader(backupConfig);
XmlNodeType type;
List<string> listFile = new List<string>();
string fileWatch = "";
//this loop is for reading XML elements and assigning to variables
while (reader.Read())
{
type = reader.NodeType;
if (type == XmlNodeType.Element)
{
if (reader.Name == "File")
{
reader.Read();
fileWatch = reader.Value;
}
else if (reader.Name == "Folder")
{
reader.Read();
fileWatch = reader.Value;
}
}
}
reader.Close();
watcher.Path = fileWatch;
watcher.Filter = "*.*";
//this loop reads whether the service will watch a file/folder
XmlTextReader reader1 = new XmlTextReader(backupConfig);
while (reader1.Read())
{
type = reader1.NodeType;
if (type == XmlNodeType.Element)
{
if (reader1.Name == "File")
{
watcher.IncludeSubdirectories = false;
watcher.Changed += new FileSystemEventHandler(OnChangedFile);
}
else if (reader1.Name == "Folder")
{
watcher.IncludeSubdirectories = true;
watcher.Changed += new FileSystemEventHandler(OnChangedFolder);
}
}
}
reader1.Close();
watcher.EnableRaisingEvents = true;
}
else
{
StreamWriter sw = new StreamWriter(serviceStat, true);
sw.WriteLine("File not found. Please start the Log Backup UI first.");
sw.Close();
}
}
I don't know what keeps the windows service not starting, the windows form simulator worked fine. What seems to be the problem?
UPDATE:
After many trials I've noticed that using only a folder directory (w/out file), the windows service doesn't work. When I replaced the fileWatch variable with a specific file (including its directory), the windows service started. When I changed it back to a folder location, it didn't work. What I think is that folder locations doesn't work in a filewatcher.
When I tried creating a new windows service that watches a folder location, it worked.. However, when I tried the same location in my original windows service, it didn't work! I was mindf$#*ed! It seems that I have to create a new windows service and build the installer everytime I place a new code/function.. This way I can keep track where I get an error.
If the service starts and stops like that, it means your code is throwing an unhandled exception. This is pretty difficult to debug, but there are a few options.
Consult the Windows Event Viewer. Normally you can get to this by going to the computer/server manager, then clicking Event Viewer -> Windows Logs -> Application. You can see what threw the exception here, which may help, but you don't get the stack trace.
Extract your program logic into a library class project. Now create two different versions of the program: a console app (for debugging), and the windows service. (This is a bit of initial effort, but saves a lot of angst in the long run.)
Add more try/catch blocks and logging to the app to get a better picture of what's going on.
Not sure this will be helpful, but for debugging a service you could always use the following in the OnStart method:
protected override void OnStart(string[] args)
{
System.Diagnostics.Debugger.Launch();
...
}
than you could attach your visual studio to the process and have better debug abilities.
hope this was helpful,
good luck
I have found it very handy to convert your existing windows service to a console by simply changing your program with the following. With this change you can run the program by debugging in visual studio or running the executable normally. But it will also work as a windows service. I also made a blog post about it
program.cs
class Program
{
static void Main()
{
var program = new YOUR_PROGRAM();
if (Environment.UserInteractive)
{
program.Start();
}
else
{
ServiceBase.Run(new ServiceBase[]
{
program
});
}
}
}
YOUR_PROGRAM.cs
[RunInstallerAttribute(true)]
public class YOUR_PROGRAM : ServiceBase
{
public YOUR_PROGRAM()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Start();
}
protected override void OnStop()
{
//Stop Logic Here
}
public void Start()
{
//Start Logic here
}
}
Please check that you have registered all HTTP endpoints in the local mahcine's Access Control List (ACL)
http://just2thepoint.blogspot.fr/2013/10/windows-service-on-local-computer.html
EventLog.Log should be set as "Application"
Meanwhile, another reason : accidentally deleted the .config file caused the same error message appears:
"Service on local computer started and then stopped. some services stop automatically..."
Use Timer and tick event to copy your files.
On start the service, start the time and specify the interval in the time.
So the service is keep running and copy the files ontick.
Hope it help.
You may want to unit test the initialization - but because it's in the OnStart method this is near to impossible. I would suggest moving the initialization code out into a separate class so that it can be tested or at least re-used in a form tester.
Secondly to add some logging (using Log4Net or similar) and add some verbose logging so that you can see details about runtime errors. Examples of runtime errors would be AccessViolation etc. especially if your service is running without enough privileges to access the config files.
The account which is running the service might not have mapped the D:-drive (they are user-specific). Try sharing the directory, and use full UNC-path in your backupConfig.
Your watcher of type FileSystemWatcher is a local variable, and is out of scope when the OnStart method is done. You probably need it as an instance or class variable.
I came across the same issue. My service is uploading/receiving XMLS and write the errors to the Event Log.
When I went to the Event Log, I tried to filter it. It prompt me that the Event Log was corrupted.
I cleared the Event Log and all OK.
In our case, nothing was added in the Windows Event Logs except logs that the problematic service has been started and then stopped.
It turns out that the service's CONFIG file was invalid. Correcting the invalid CONFIG file fixed the issue.

How do I generate keystrokes in a non-form application

So I have a huge program and decided I should make one of the methods run in a separate thread. So I put the method in a separate class, an activated it on my form. It seemed to worked just how I wanted it to until it got to part where it gave me this error:
SendKeys cannot run inside this application because the application
is not handling Windows messages. Either change the application to
handle messages, or use the SendKeys.SendWait method.
I tried looking for the answer online. I think I saw something about how SendKeys only works in a Form or something.
Can anyone tell me a way to simulate a keystroke without using SendKeys, OR a way to get SendKeys to work in a different, non-form thread?
Your console application needs a message loop. This is done through the Application class. You will need to call Application.Run(ApplicationContext).
class MyApplicationContext : ApplicationContext
{
[STAThread]
static void Main(string[] args)
{
// Create the MyApplicationContext, that derives from ApplicationContext,
// that manages when the application should exit.
MyApplicationContext context = new MyApplicationContext();
// Run the application with the specific context. It will exit when
// the task completes and calls Exit().
Application.Run(context);
}
Task backgroundTask;
// This is the constructor of the ApplicationContext, we do not want to
// block here.
private MyApplicationContext()
{
backgroundTask = Task.Factory.StartNew(BackgroundTask);
backgroundTask.ContinueWith(TaskComplete);
}
// This will allow the Application.Run(context) in the main function to
// unblock.
private void TaskComplete(Task src)
{
this.ExitThread();
}
//Perform your actual work here.
private void BackgroundTask()
{
//Stuff
SendKeys.Send("{RIGHT}");
//More stuff here
}
}
I Know this not an answer, but this how i used to do using ActiveX and Script
Set ws = CreateObject("WScript.Shell")
str = "Hi there... ~ Dont click your mouse while i am typing." & _
" ~~This is a send key example, using which you can send your keystrokes"
ws.Run("notepad.exe")
WScript.Sleep(1000)
For c=1 To Len(str)
WScript.Sleep(100) 'Increase the value for longer delay
ws.SendKeys Mid(str,c,1)
Next
Save this code as file.vbs and double click to execute in windows machine.

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