4

I have window service which acts as a sync software. I want to add unhanded exception logging on my service, so I modified my program.cs like this:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
    static void Main()
    {
        // Register Unhandled Exception Handler
        AppDomain.CurrentDomain.UnhandledException +=
            new UnhandledExceptionEventHandler(UnhandledExceptionHandler);

        // Run Service
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] 
        { 
            new Service() 
        };
        ServiceBase.Run(ServicesToRun);
    }

    static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
    {
        // Get Exception
        Exception ex = (Exception)args.ExceptionObject;

        // Generate Error
        string ErrorMessage = String.Format(
            "Error: {0}\r\n" +
            "Runtime Terminating: {1}\r\n----- ----- ----- ----- ----- -----\r\n\r\n" +
            "{2}\r\n\r\n####################################\r\n",
                ex.Message,
                args.IsTerminating,
                ex.StackTrace.Trim());

        // Write Error To File
        try
        {
            using (StreamWriter sw = File.AppendText("UnhandledExceptions.log"))
                sw.WriteLine(errorMessage);
        }
        catch { }
    }
}

Then on my Service.cs file, in the OnStart method, I added a throw new Exception("test"); to see if unhanded exceptions are being logged to file as expected.

When I start my service, it stops immediately as expected; however it doesn't seem to be logging the exception to the specified file.

Any idea what I am doing wrong here? Thanks in advance for any help.

Before you ask, my service runs as Local Service and the directory where my service .exe runs from (c:\mysync) already has Local Service added in the security tab with full read/write access.

Latheesan
  • 23,247
  • 32
  • 107
  • 201
  • The service framework is most likely catching the exception--so it is not going "unhandled", thus the event won't get triggered. – Matt Smith Apr 16 '14 at 16:04
  • Is your `StreamWriter` instance definitely flushing its contents before its disposed of? Try using the `File.AppendAllText(string, string)` method instead. – Martin Costello Apr 16 '14 at 16:18

2 Answers2

5

OnStart is called in Service base class inside try-catch block. If an exception happens on this stage it catches it and just set a status 1 as a result and do not throw it further:

  string[] args = (string[]) state;
  try
  {
    this.OnStart(args);
    .....
  }
  catch (Exception ex)
  {
    this.WriteEventLogEntry(Res.GetString("StartFailed", new object[1]
    {
      (object) ((object) ex).ToString()
    }), EventLogEntryType.Error);
    this.status.currentState = 1;
  }

As a result you can find a record in EventLogs, but you can't catch it as an unhanded domain exception, as there is no such exception.

Igor Tkachenko
  • 1,120
  • 7
  • 17
  • 1
    Spot on. There were no "unhanded" exception occurring. As you've detailed; the exception was automatically getting logged in the EventLogs. Thanks. – Latheesan Apr 17 '14 at 07:41
1
   using (StreamWriter sw = File.AppendText("UnhandledExceptions.log"))

It is forever a really bad idea to not use full path names for files (like c:\foo\bar.log). Especially in a service, you have very little control over the default directory for your service. Because it is started by the service control manager, not by the user from the command prompt or a desktop shortcut.

So high odds that you are just looking at the wrong file. The real one probably ended up being written to c:\windows\system32 (or syswow64). The operating system directories are normally write protected but that doesn't work for a service, they run with a highly privileged account so can litter the hard drive anywhere.

Always use full path names. Using the EventLog instead is highly recommended.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • It has not been written anywhere as an exception has been caught by the framework, see code above. – Igor Tkachenko Apr 17 '14 at 13:37
  • Well, that heavily depends on where the code appears. Fine if it is in one of the ServiceBase method overrides, certainly not fine anywhere else. Which is the likely case. – Hans Passant Apr 17 '14 at 13:55
  • It's in ServiceBase code, methods: private void ServiceQueuedMainCallback(object state). There is no way to override it, unfortunately. – Igor Tkachenko Apr 17 '14 at 14:01
  • Sure, that's the internal method that calls OnStart, OnStop, OnPause, etc. The ones you have to override in your own class. OnStart() typically starts a thread or a timer, OnStop() stops it again. Any exception in such a thread doesn't automatically get logged, there of course is no backstop like there is in the service controller callback. – Hans Passant Apr 17 '14 at 14:06
  • Yes, if the exception is thrown in a new thread, it will be caught by "global" handler. But in case described in the question: "in the OnStart method, I added a throw new Exception("test");", there is no possibility to cache such exception by "global" handler. – Igor Tkachenko Apr 17 '14 at 14:09
  • Sure, a good reason to call the event handler directly from a catch block so all logging is done the same way. – Hans Passant Apr 17 '14 at 14:26
  • "Always use full path names." – How would would you choose a valid and sensible full path for your windows service log files? Suppose you want to create the log files in a specific directory located in the installation directory of your software. Would you use reflection? – Luca Cremonesi Apr 15 '15 at 18:38
  • I use Assembly.GetExecutingAssembly().Location – Hans Passant Apr 15 '15 at 22:04