3

I have a non-interactive application where I need to load some string fields before a log file is instantiated. There is a method that reads the contents of a text file into a field. If the path does not exist, I want the exception in the application event log to list the path which was attempted to be accessed. Here's what I tried:

try 
{
    string contents = File.ReadAllText(path);
}
catch (FileNotFoundException)
{
    throw new FileNotFoundException(string.Format("Path not found: {0}",path));
}

When I run my test application with this code, the exception text in the console window is as expected:

Unhandled Exception: System.IO.FileNotFoundException: Path not found: C:\temp\notthere.txt

However, the exception details recorded in the event log do not include Exception message text:

Application: ConsoleApp3.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileNotFoundException
    at ConsoleApp3.Program.Main(System.String[])

Is it possible to get .NET to log more exception details automatically, or do I just need to add code to write to the event log myself?

Mike Bruno
  • 600
  • 2
  • 9
  • 26
  • 2
    Your catching `FileNotFoundException` exceptions and throwing new ones, thus loosing potentially helpful details. At the very least attach the previous exception as an inner exception. – Ryan Searle Aug 24 '18 at 13:53

2 Answers2

1

One approach you can take is to handle the AppDomain.CurrentDomain.UnhandledException event:

using System;
using System.Diagnostics;

namespace Scratch
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            throw new Exception("Uh oh!");
        }

        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            using (var eventLog = new EventLog("Application"))
            {
                eventLog.Source = "Application";
                eventLog.WriteEntry($"Unhandled exception {(e.ExceptionObject as Exception).Message}");
            }
        }
    }
}

By handling this event you get an opportunity to react to the Exception, which you can write to the event log yourself, or to your own logging target.

Do be mindful of the fact that when you're in this exception handler you really should treat the rest of your applications infrastructure as probably broken and avoid doing too much other than capturing the data you need.

Rob
  • 45,296
  • 24
  • 122
  • 150
  • Thanks, but I was wondering if there was a way to get .NET to include the exception message in the event log entry it creates natively. It's looking like the answer is "no", but I appreciate your input. – Mike Bruno Aug 25 '18 at 00:47
  • @MikeBruno, not that I'm aware of, I'm afraid :( My only guess here is that there's a maximum limit to the size of an event log entry and having the "core" .net infrastructure include content that could exceed that is the reason that it's not an option :) – Rob Aug 26 '18 at 20:10
0

You can create a "named" exception, since CLR supports arbitrary identifier (per .Net / CLR Identifiers). Creation codes are from How to dynamically create a class?.

public static T Compile<T>(this T ex) where T : Exception {
    var typeSignature = $"{ex.Message}Exception".Normalize(System.Text.NormalizationForm.FormC);
    var an = new AssemblyName("MyAssembly");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
    TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
            TypeAttributes.Public |
            TypeAttributes.Class |
            TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass |
            TypeAttributes.BeforeFieldInit |
            TypeAttributes.AutoLayout,
            ex.GetType());
    tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
    var objectType = tb.CreateType();
    var myObject = Activator.CreateInstance(objectType);
    return (T)myObject;
}

Use like this:

try {
    string contents = File.ReadAllText(path);
} catch (FileNotFoundException) {
    throw new FileNotFoundException(string.Format("Path not found: {0}",path)).Compile();
}
SE12938683
  • 196
  • 1
  • 7