Configuring log4net programmatically, but extra logging going to console - c#

I've taken the code from Can you configure log4net in code instead of using a config file? and put it into my little NET5 application. The log file, thus far, seems okay, but as I haven't configured any appender going to the console, but only the one rolling file appender, I wasn't expecting to get anything going to the console. However, that is exactly what is happening - my messages are going to the console albeit in a different format, it's also not using my pattern layout.
This is what I have for setup at the moment:
private void SetupLog4Net()
{
var hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.RemoveAllAppenders();
hierarchy.Root.Level = Level.Debug;
var patternLayout = new PatternLayout
{
ConversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff} [%logger] %message%newline%exception",
};
patternLayout.ActivateOptions();
var rollingFileAppender = new RollingFileAppender
{
AppendToFile = true,
File = #"Logs/uav.log",
Layout = patternLayout,
MaxSizeRollBackups = 5,
MaxFileSize = 1_000_000,
RollingStyle = RollingFileAppender.RollingMode.Size,
StaticLogFileName = true,
Encoding = System.Text.Encoding.UTF8,
};
rollingFileAppender.ActivateOptions();
hierarchy.Root.AddAppender(rollingFileAppender);
hierarchy.Root.Level = Level.Info;
hierarchy.Configured = true;
BasicConfigurator.Configure(hierarchy);
}
This is called as the first thing in the first thing that Main does (construct the application object, this is called via that constructor), before we get to absolutely anything else.
Then, later, when we call to log stuff, e.g.,
var logger = LogManager.GetLogger(this.GetType());
logger.Info(message.Message, message.Exception);
I get output such as:
1780 [6] INFO MyClass.MainApp (null) - Ready
I can't even make sense of half of that, but making sense isn't the point - clearing out this appender is. Comments on the actual configuration are fine as comments, but the main question is just how to remove that console output.
Using Log4NET 2.0.12 and .NET 5.0.400 on Linux

According to the log4net configuration page, calling BasicConfigurator.Configure() method will "Set up a simple configuration that logs on the console."
Seeing as you've configured things programmatically to write to a file, no need to include the line:
BasicConfigurator.Configure();

OK: so your REAL question is "How do I disable Log4Net console logging?"
Remember: in Java, "log4j"is "the logger". In .Net, however, Log4net coexists with Microsoft.Extensions.Logging. So there's "extra stuff", which might yield "surprises". .Net itself (e.g. Net 5, nee ".Net Core") might also yield "surprises".
Specifically, there are several solutions discussed here:
How do I disable log4net status messages to the console?
a. Change log4net.Config.BasicConfigurator.Configure(); to
log4net.Config.XmlConfigurator.Configure();
b. set debug = false
c. Modify "log4net.Internal.Debug" in your app config file (e.g.
"appsettings.json")
<appSettings>
<add key="log4net.Internal.Debug" value="false"/>
Also look at the Log4Net FAQs:
http://logging.apache.org/log4net/release/faq.html
Internal debugging messages are written to the console and to the
System.Diagnostics.Trace system. If the application does not have a
console the messages logged there will be lost. Note that an
application can redirect the console stream by setting the
System.Console.Out.
As log4net internal debug messages are written to the
System.Diagnostics.Trace system it is possible to redirect those
messages to a local file.

Related

asp.NET c# : Code working as a console app but not as a web app

My basic problem was converting a .docx file to .pdf. The problem would be solved incase I was allowed to use Microsoft.Office.Interop.Word.dll, which i am not since the server will not have MS Office installed. So I needed a free/open-source library that would allow me to do so. And i came across docx4j.NET.
http://www.docx4java.org/blog/2014/09/docx-to-pdf-in-c-net/
This worked fine as long as I ran it as a Console App. The following is the concerned code snippet:
string fileIN = #"C:\Users\...\Visual Studio 2013\Projects\HRDapp\HRDapp\Letter_Templates\AP.docx";
string fileOUT = #"C:\Users\...\Visual Studio 2013\Projects\HRDapp\HRDapp\Letter_Templates\AP.pdf";
log.Info("Hello from Common Logging");
// Necessary, if slf4j-api and slf4j-NetCommonLogging are separate DLLs
ikvm.runtime.Startup.addBootClassPathAssembly(
System.Reflection.Assembly.GetAssembly(
typeof(org.slf4j.impl.StaticLoggerBinder)));
// Configure to find docx4j.properties
// .. add as URL the dir containing docx4j.properties (not the file itself!)
Plutext.PropertiesConfigurator.setDocx4jPropertiesDir(projectDir + #"src\samples\resources\");
java.io.File file = new java.io.File(fileIN);
// OK, do it..
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(file);
java.io.FileOutputStream fos = new java.io.FileOutputStream(new java.io.File(fileOUT));
org.docx4j.Docx4J.toPDF(wordMLPackage, fos);
fos.close();
In case of using this in a Web App, the code runs fine till
java.io.File file = new java.io.File(fileIN);
and gets stuck at
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(file);
Although the file path is correct and works fine in the console app, but there seems something else that I am missing here. The log also prints upto the following statement-
iisexpress.exe Information: 0 : [INFO] org.docx4j.jaxb.Context - Using Java 6/7 JAXB implementation
.. and stops. Any kind of reply directing me to the source of the error will be very helpful. Thanks.
As Jeroen (of IKVM fame) has explained, when there is no main assembly (eg in an ASP.NET application), the IKVM class loader can't find your assembly when the code is trying to dynamically load a class.
So you'll want to add not just:
ikvm.runtime.Startup.addBootClassPathAssembly(
System.Reflection.Assembly.GetAssembly(
typeof(org.slf4j.impl.StaticLoggerBinder)));
but also:
ikvm.runtime.Startup.addBootClassPathAssembly(
System.Reflection.Assembly.GetAssembly(
typeof(org.slf4j.LoggerFactory)));
ikvm.runtime.Startup.addBootClassPathAssembly(
System.Reflection.Assembly.GetAssembly(
typeof(org.docx4j.jaxb.Context)));

Change Enterprise Library configuration midway in a program

I want to log to a particular set of files/folders for some time, and then switch and start logging to a different set of files. For this, I'm using the fluent API to set my filename as I want (not shown here). But I'm not able to change the configuration midway in a program. It is not working the way I expected it. Are there any commands to reload or clear the configuration that I need to do if I'm setting up the config a second time?
If I comment out FirstConfig(); and the next Log statement, then the SecondConfig() works fine. Else, only FirstConfig() seems to have an effect.
static void Main(string[] args)
{
FirstConfig();
Logger.LogInfoMessage("Before processing"); //Some wrapper around EntLib logger methods
//Do some processing for some time
SecondConfig();
Logger.LogInfoMessage("After after processing");
}
private static void FirstConfig()
{
var textFormatter = new FormatterBuilder()
.TextFormatterNamed("First Text Formatter")
.UsingTemplate("{message}");
var builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions.DoNotRevertImpersonation()
.LogToCategoryNamed("General").WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile("First Listener")
.FormatWith(textFormatter).WithHeader("").WithFooter("")
.ToFile("Logs\\BeforeChange.log");
var configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
}
private static void SecondConfig()
{
var textFormatter = new FormatterBuilder()
.TextFormatterNamed("Second Text Formatter")
.UsingTemplate("{message}");
var builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions.DoNotRevertImpersonation()
.LogToCategoryNamed("General").WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile("Second Listener")
.FormatWith(textFormatter).WithHeader("").WithFooter("")
.ToFile("Logs\\AfterChange.log");
var configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
}
The last line is creating a new container and assigning the configuration to it, you're creating two different containers.
I think enterprise library allows detection of an existing logging configuration change to be recognized without an application restart. It's possible you want to just modify the existing configuration and then handle the change.
http://msdn.microsoft.com/en-us/library/ff664363%28PandP.50%29.aspx
All configuration source classes implement the IConfigurationSource interface. This interface allows your application code to subscribe to notifications of configuration changes. For more information, see Updating Configuration Settings at Run Time. By default, in Enterprise Library, only the Logging Application Block registers to receive notifications of configuration changes.
For Enterprise Library 5.0
http://msdn.microsoft.com/en-us/library/ff664640%28v=pandp.50%29.aspx
"The one exception to this is the Logging Application Block, which is able to detect configuration changes and reload the configuration without restarting the application. This works for Web Forms and Windows Forms applications, though it will still automatically trigger the application to restart for Web Forms applications. This means that you cannot rely on maintenance of in-process session state in ASP.NET applications when you change the configuration file. "
You can detect changes to the configuration by registering for the SourceChanged event that is defined in the IConfigurationSource interface and implemented by all Enterprise Library configuration sources. You can register and unregister for this event using the standard event handling approach, as shown in the following example that detects changes to configuration stored in a custom file named MyConfig.config.

FOP and IKVM in .NET - Images Not Working

UPDATE2: I got it working completely now! Scroll way down to find out how...
UPDATE: I got it working! Well... partially. Scroll down for the answer...
I'm trying to get my FO file to show an external image upon transforming it to PDF (or RTF for that matter, but I'm not sure whether RTFs are even capable of displaying images (they are)) with FOP, but I can't seem to get it working. (The question asked here is different than mine.)
I am using IKVM 0.46.0.1 and have compiled a FOP 1.0 dll to put in .NET; this code worked fine when I didn't try to add images:
private void convertFoByMimetype(java.io.File fo, java.io.File outfile, string mimetype)
{
OutputStream output = null;
try
{
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// configure foUserAgent as desired
// Setup outputput stream. Note: Using BufferedOutputStream
// for performance reasons (helpful with FileOutputStreams).
output = new FileOutputStream(outfile);
output = new BufferedOutputStream(output);
// Construct fop with desired output format
Fop fop = fopFactory.newFop(mimetype, foUserAgent, output);
// Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(); // identity transformer
// Setup input stream
Source src = new StreamSource(fo);
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Start XSLT transformation and FOP processing
transformer.transform(src, res);
}
catch (Exception ex)
...
}
However, when I (or rather a DocBook2FO transformation) added the following code:
<fo:external-graphic src="url(images/interface.png)" width="auto" height="auto" content-width="auto" content-height="auto" content-type="content-type:image/png"></fo:external-graphic>
into the FO file, the image did not show. I read through a bit of the FAQ on Apache's site, which says:
3.3. Why is my graphic not rendered?
Most commonly, the external file is not being found by FOP. Check the
following:
Empty or wrong baseDir setting.
Spelling errors in the file name (including using the wrong case).
...
Other options did not seem to be my case (mainly for the reason below - "The Weird Part"). I tried this:
...
try
{
fopFactory.setBaseURL(fo.getParent());
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
foUserAgent.setBaseURL(fo.getParent());
FOURIResolver fourir = fopFactory.getFOURIResolver();
foUserAgent.setURIResolver(fourir);
// configure foUserAgent as desired
...
with no avail.
The Weird Part
When I use the command-line implementation of FOP, it works fine and displays my image with no problem. (I don't want to go the run-command-line-from-program route, because I don't want to force the users to install Java AND the .NET framework when they want to use my program.)
The png file is generated from GDI+ from within my application (using Bitmap.Save). I also tried different png files, but none of them worked for me.
Is there anything I might be missing?
Thanks a bunch for getting this far
UPDATE and possible answer
So I might have figured out why it didn't work. I put some time into studying the code (before I basically just copypasted it without thinking about it much). The problem is indeed in the wrong basedir setting.
The key is in this chunk of code:
// Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(); // identity transformer
// Setup input stream
Source src = new StreamSource(fo);
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Start XSLT transformation and FOP processing
transformer.transform(src, res);
What happens here is an identity transformation, which routes its own result into an instance of FOP I've created before. This effectively changes the basedir of the routed FO into that of the application's executable. I have yet to figure out how to do this without a transformation and route my input directly into FOP, but for the moment I worked around this by copying my images into the executable's directory.
Which is where another problem came in. Now whenever I try to execute the code, I get an exception at the line that says transformer.transform(src, res);, which confuses the pants out of me, because it doesn't say anything. The ExceptionHelper says:
java.lang.ExceptionInInitializerError was caught
and there is no inner exception or exception message. I know this is hard to debug just from what I wrote, but I'm hoping there might be an easy fix.
Also, this e-mail seems vaguely related but there is no answer to it.
UPDATE2
Finally, after a few sleepless nights, I managed to get it working with one of the simplest ways possible.
I updated IKVM, compiled fop with the new version and replaced the IKVM references with the new dlls. The error no longer occurs and my image renders fine.
I hope this helps someone someday
I'm using very similar code, although without the FOUserAgent, Resolvers, etc. and it works perfectly.
Did you try setting the src attribute in the XSLT without the url() function?
What might help you diagnose the problem further are the following statements:
java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
java.lang.System.setErr(New java.io.PrintStream(New TraceStream(TraceStream.Level.Error)))
java.lang.System.setOut(New java.io.PrintStream(New TraceStream(TraceStream.Level.Info)))
Where TraceStream is a .NET implementation of a java.io.OutputStream which writes to your favorite logger.
I posted a version for the Common.Logging package at http://pastebin.com/XH1Wg7jn.
Here's a post not to leave the question unanswered, see "Update 1" and "Update 2" in the original post for the solution.

Why multiple log files are getting created with GUID using System.Diagnostics c# [duplicate]

I use TextWriterTraceListener (System.Diagnostics) in my application to trace several things like exceptions,...
The application is running on a terminal server and if there are many users using it simultaneously the listener starts to create many tracefiles with random GUIDs in the filename.
Are there possibilities or workarounds to avoid this behaviour ?
I've just taken a look at the documentation for TextWriterTraceListener and there's a note about 1/3 of the way down the page
If an attempt is made to write to a file that is in use or unavailable, the file name is automatically prefixed by a GUID
So, this would appear to be by design. If the file is indeed unavailable then there's nothing that can be done about it with the current implementation. What you could try doing is writing a custom implementation of TextWriterTraceListener that overrides the relevant Write/WriteLine methods so that the output goes to a file, per user, with a name that better suits your needs.
If what you want is for ALL logging from ALL users on the Terminal Server to go to a single file, then you'll almost certainly need to have some kind of "3rd party" process running that "owns" the file and synchronises writes to it, such as a Windows Service that is then called by your custom TextWriterTraceListener
Was the fix calling the Trace.Listeners.Add(xxx listener) multiple times on accident?
Because if you have multiple listeners added they write too all listeners when you call the Trace.writeline();
Also local IIS might be continueing to have the file in use when you shut down the application.
I am currently testing the addition of System.Diagnostics.Trace.Listeners.Clear() in my output method...
// Upon a new day re-create the TextWriterTraceListener to update our file name...
if (_date?.Day != DateTime.Now.Day) { _listener = null; }
if (_listener == null)
{
System.Diagnostics.Trace.Listeners.Clear();
_fileName = $"{DateTime.Now.ToString("yyyy-MM-dd")}_Trace.json";
// Add a writer that appends to the trace.log file:
_listener = new System.Diagnostics.TextWriterTraceListener(_fileName);
_listener.IndentSize = 4;
_listener.TraceOutputOptions = System.Diagnostics.TraceOptions.None; // TraceOptions.DateTime | TraceOptions.ThreadId;
System.Diagnostics.Trace.AutoFlush = true;
System.Diagnostics.Trace.Listeners.Add(_listener);
// Obtain the Console's output stream, then add that as a listener...
System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
}

What is the most reliable way to create a custom event log and event source during the installation of a .Net Service

I am having difficulty reliably creating / removing event sources during the installation of my .Net Windows Service.
Here is the code from my ProjectInstaller class:
// Create Process Installer
ServiceProcessInstaller spi = new ServiceProcessInstaller();
spi.Account = ServiceAccount.LocalSystem;
// Create Service
ServiceInstaller si = new ServiceInstaller();
si.ServiceName = Facade.GetServiceName();
si.Description = "Processes ...";
si.DisplayName = "Auto Checkout";
si.StartType = ServiceStartMode.Automatic;
// Remove Event Source if already there
if (EventLog.SourceExists("AutoCheckout"))
EventLog.DeleteEventSource("AutoCheckout");
// Create Event Source and Event Log
EventLogInstaller log = new EventLogInstaller();
log.Source = "AutoCheckout";
log.Log = "AutoCheckoutLog";
Installers.AddRange(new Installer[] { spi, si, log });
The facade methods referenced just return the strings for the name of the log, service, etc.
This code works most of the time, but recently after installing I started getting my log entries showing up in the Application Log instead of the custom log. And the following errors are in the log as well:
The description for Event ID ( 0 ) in Source ( AutoCheckout ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help and Support for details.
For some reason it either isn't properly removing the source during the uninstall or it isn't creating it during the install.
Any help with best practices here is appreciated.
Thanks!
In addition, here is a sample of how I am writing exceptions to the log:
// Write to Log
EventLog.WriteEntry(Facade.GetEventLogSource(), errorDetails, EventLogEntryType.Error, 99);
Regarding stephbu's answer: The recommended path is an installer script and installutil, or a Windows Setup routine.
I am using a Setup Project, which performs the installation of the service and sets up the log. Whether I use the installutil.exe or the windows setup project I believe they both call the same ProjectInstaller class I show above.
I see how the state of my test machine could be causing the error if the log isn't truly removed until rebooting. I will experiment more to see if that solves the issue.
Edit:
I'm interested in a sure fire way to register the source and the log name during the installation of the service. So if the service had previously been installed, it would remove the source, or reuse the source during subsequent installations.
I haven't yet had an opportunity to learn WiX to try that route.
The ServiceInstaller class automatically creates an EventLogInstaller and puts it inside its own Installers collection.
Try this code:
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceProcessInstaller.Password = null;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
// serviceInstaller
ServiceInstaller serviceInstaller = new ServiceInstaller();
serviceInstaller.ServiceName = "MyService";
serviceInstaller.DisplayName = "My Service";
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.Description = "My Service Description";
// kill the default event log installer
serviceInstaller.Installers.Clear();
// Create Event Source and Event Log
EventLogInstaller logInstaller = new EventLogInstaller();
logInstaller.Source = "MyService"; // use same as ServiceName
logInstaller.Log = "MyLog";
// Add all installers
this.Installers.AddRange(new Installer[] {
serviceProcessInstaller, serviceInstaller, logInstaller
});
Couple of things here
Creating Event Logs and Sources on the fly is pretty frowned upon. primarily because of the rights required to perform the action - you don't really want to bless your applications with that power.
Moreover if you delete an event log or source the entry is only truely deleted when the server reboots, so you can get into wierd states if you delete and recreate entries without bouncing the box. There are also a bunch of unwritten rules about naming conflicts due to the way the metadata is stored in the registry.
The recommended path is an installer script and installutil, or a Windows Setup routine.
The best recommendation would be to not use the Setup Project in Visual Studio. It has very severe limitations.
I had very good results with WiX
I have to agree with stephbu about the "weird states" that the event log gets into, I've run into that before. If I were to guess, some of your difficulties lie there.
However, the best way that I know of to do event logging in the application is actually with a TraceListener. You can configure them via the service's app.config:
http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlogtracelistener.aspx
There is a section near the middle of that page that describes how to use the EventLog property to specify the EventLog you wish to write to.
Hope that helps.
I also followed helb's suggestion, except that I basically used the standard designer generated classes (the default objects "ServiceProcessInstaller1" and "ServiceInstaller1"). I decided to post this since it is a slightly simpler version; and also because I am working in VB and people sometimes like to see the VB-way.
As tartheode said, you should not modify the designer-generated ProjectInstaller class in the ProjectInstaller.Designer.vb file, but you can modify the code in the ProjectInstaller.vb file. After creating a normal ProjectInstaller (using the standard 'Add Installer' mechanism), the only change I made was in the New() of the ProjectInstaller class. After the normal "InitializeComponent()" call, I inserted this code:
' remove the default event log installer
Me.ServiceInstaller1.Installers.Clear()
' Create an EventLogInstaller, and set the Event Source and Event Log
Dim logInstaller As New EventLogInstaller
logInstaller.Source = "MyServiceName"
logInstaller.Log = "MyCustomEventLogName"
' Add the event log installer
Me.ServiceInstaller1.Installers.Add(logInstaller)
This worked as expected, in that the installer did not create the Event Source in the Application log, but rather created in the new custom log file.
However, I had screwed around enough that I had a bit of a mess on one server. The problem with the custom logs is that if the event source name exists associated to the wrong log file (e.g. the 'Application' log instead of your new custom log), then the source name must first be deleted; then the machine rebooted; then the source can be created with association to the correct log. The Microsoft Help clearly states (in the EventLogInstaller class description):
The Install method throws an exception
if the Source property matches a
source name that is registered for a
different event log on the computer.
Therefore, I also have this function in my service, which is called when the service starts:
Private Function EventLogSourceNameExists() As Boolean
'ensures that the EventSource name exists, and that it is associated to the correct Log
Dim EventLog_SourceName As String = Utility.RetrieveAppSetting("EventLog_SourceName")
Dim EventLog_LogName As String = Utility.RetrieveAppSetting("EventLog_LogName")
Dim SourceExists As Boolean = EventLog.SourceExists(EventLog_SourceName)
If Not SourceExists Then
' Create the source, if it does not already exist.
' 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.
'So pass back a False to cause the service to terminate. User will have
'to re-start the application to make it work. This ought to happen only once on the
'machine on which the service is newly installed
EventLog.CreateEventSource(EventLog_SourceName, EventLog_LogName) 'create as a source for the SMRT event log
Else
'make sure the source is associated with the log file that we want
Dim el As New EventLog
el.Source = EventLog_SourceName
If el.Log <> EventLog_LogName Then
el.WriteEntry(String.Format("About to delete this source '{0}' from this log '{1}'. You may have to kill the service using Task Manageer. Then please reboot the computer; then restart the service two times more to ensure that this event source is created in the log {2}.", _
EventLog_SourceName, el.Log, EventLog_LogName))
EventLog.DeleteEventSource(EventLog_SourceName)
SourceExists = False 'force a close of service
End If
End If
Return SourceExists
End Function
If the function returns False, the service startup code simply stops the service. This function pretty much ensures that you will eventually get the correct Event Source name associated to the correct Event Log file. You may have to reboot the machine once; and you may have to try starting the service more than once.
I am having the same problems. In my case it seems that Windows installer is adding the event source which is of the same name as my service automatically and this seems to cause problems. Are you using the same name for the windows service and for the log source? Try changing it so that your event log source is called differently then the name of the service.
I just posted a solution to this over on MSDN forums which was to that I managed to get around this using a standard setup MSI project. What I did was to add code to the PreInstall and Committed events which meant I could keep everything else exactly as it was:
SortedList<string, string> eventSources = new SortedList<string, string>();
private void serviceProcessInstaller_BeforeInstall(object sender, InstallEventArgs e)
{
RemoveServiceEventLogs();
}
private void RemoveServiceEventLogs()
{
foreach (Installer installer in this.Installers)
if (installer is ServiceInstaller)
{
ServiceInstaller serviceInstaller = installer as ServiceInstaller;
if (EventLog.SourceExists(serviceInstaller.ServiceName))
{
eventSources.Add(serviceInstaller.ServiceName, EventLog.LogNameFromSourceName(serviceInstaller.ServiceName, Environment.MachineName));
EventLog.DeleteEventSource(serviceInstaller.ServiceName);
}
}
}
private void serviceProcessInstaller_Committed(object sender, InstallEventArgs e)
{
RemoveServiceEventLogs();
foreach (KeyValuePair<string, string> eventSource in eventSources)
{
if (EventLog.SourceExists(eventSource.Key))
EventLog.DeleteEventSource(eventSource.Key);
EventLog.CreateEventSource(eventSource.Key, eventSource.Value);
}
}
The code could be modified a bit further to only remove the event sources that didn't already exist or create them (though the logname would need to be stored somewhere against the installer) but since my application code actually creates the event sources as it runs then there's no point for me. If there are already events then there should already be an event source. To ensure that they are created, you can just automatically start the service.
I experienced some similar weird behaviour because I tried to register an event source with the same name as the service I was starting.
I notice that you also have the DisplayName set to the same name as your event Source.
On starting the service up, we found that Windows logged a "Service started successfully" entry in the Application log, with source as the DisplayName. This seemed to have the effect of registering Application Name with the application log.
In my event logger class I later tried to register Application Name as the source with a different event log, but when it came to adding new event log entries they always got added to the Application log.
I also got the "The description for Event ID ( 0 ) in Source" message several times.
As a work around I simply registered the message source with a slightly different name to the DisplayName, and it's worked ever since. It would be worth trying this if you haven't already.
The problem comes from installutil which by default registers an event source with your services name in the "Application" EventLog. I'm still looking for a way to stop it doing this crap. It would be really nice if one could influence the behaviour of installutil :(
Following helb's suggestion resolved the problem for me. Killing the default event log installer, at the point indicated in his example, prevented the installer from automatically registering my Windows Service under the Application Event log.
Far too much time was lost attempting to resolve this frustrating quirk. Thanks a million!
FWIW, I could not modify the code within my designer-generated ProjectInstaller class without causing VS to carp about the mods. I scrapped the designer-generated code and manually entered the class.
Adding an empty registry key to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application\MY_CUSTOM_SOURCE_NAME_HERE seems to work fine.
An easy way to change the default behavior (that is, that the project installer creates an event log source with the name of your service in the application log) is to easily modify the constructor of the project installer as following:
[RunInstaller( true )]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
//Skip through all ServiceInstallers.
foreach( ServiceInstaller ThisInstaller in Installers.OfType<ServiceInstaller>() )
{
//Find the first default EventLogInstaller.
EventLogInstaller ThisLogInstaller = ThisInstaller.Installers.OfType<EventLogInstaller>().FirstOrDefault();
if( ThisLogInstaller == null )
continue;
//Modify the used log from "Application" to the same name as the source name. This creates a source in the "Applications and Services log" which separates your service logs from the default application log.
ThisLogInstaller.Log = ThisLogInstaller.Source;
}
}
}

Categories