Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write a caught ASP.NET exception to EventLog without losing detail

This article explains in detail how to log an ASP.NET exception to the Windows EventLog and display a custom error page to the end-user.

However, the standard Event Logging mechanism for an ASP.NET web application automatically includes a lot of useful information not shown in this article. Implementing the code in the article results in a loss of detail/granularity in my error Event.

For example with automatic uncaught exception logging, you can see many attributes under the headings: Event Information, Application Information, Process Information, Request Information, Thread Information, Custom Event Details.

How can I implement logging of all of the same information that is logged in an uncaught exception and append my custom information into the section Custom Event Details? The best answer should preferably use some inbuilt method(s) of System.Diagnostics or System.Exception or similar i.e. writing as little code as possible to write the log entry with all sections mentioned above and simply append any custom details to the string.

If it is possible, I would also like to return the unique hashed Event ID (example b68b3934cbb0427e9497de40663c5225 from below) back to the application for display on my ErrorPage.aspx

Example of log format required:

Event code: 3005 
Event message: An unhandled exception has occurred. 
Event time: 15/07/2016 15:44:01 
Event time (UTC): 15/07/2016 14:44:01 
Event ID: b68b3934cbb0427e9497de40663c5225 
Event sequence: 131 
Event occurrence: 2 
Event detail code: 0 

Application information: 
    Application domain: /LM/W3SVC/3/ROOT-1-131130657267252632 
    Trust level: Full 
    Application Virtual Path: / 
    Application Path: C:\WWW\nobulus\nobulusPMM\Application\PMM\ 
    Machine name: L-ADAM 

Process information: 
    Process ID: 47216 
    Process name: iisexpress.exe 
    Account name: L-ADAM\Adam 

Exception information: 
    Exception type: ApplicationException 
    Exception message: Error running stored procedure saveValidation: Procedure or function 'saveValidation' expects parameter '@ValidatedBy', which was not supplied.
   at PMM.Models.PMM_DB.runStoredProcedure(String StoredProcedureName, List`1 SQLParameters) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Models\PMM_DB.cs:line 104
   at PMM.Models.PMM_DB.saveValidation(String PTLUniqueID, String ValidatedBy, DateTime ValidationDateTime, Nullable`1 ValidationCategoryID, String ValidationCategory,     String Comment, Nullable`1 ClockStartDate, Nullable`1 ClockStopDate, String StartRTTStatus, String StopRTTStatus, String LastRTTStatus, Boolean MergedPathway, String     MergedPathwayID, String ExtinctPathwayID, DataTable ChecklistResponses) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Models\PMM_DB.cs:line 265
   at PMM.Validate.lnkSaveButton_Click(Object sender, EventArgs e) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Validate.aspx.cs:line 323
   at System.Web.UI.WebControls.LinkButton.OnClick(EventArgs e)
   at System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)



Request information: 
    Request URL: http://localhost:6901/Validate?PTLUniqueID=RTT10487 
    Request path: /Validate 
    User host address: ::1 
    User: L-ADAM\Adam 
    Is authenticated: True 
    Authentication Type: Negotiate 
    Thread account name: L-ADAM\Adam 

Thread information: 
    Thread ID: 19 
    Thread account name: L-ADAM\Adam 
    Is impersonating: False 
    Stack trace:    at PMM.Models.PMM_DB.runStoredProcedure(String StoredProcedureName, List`1 SQLParameters) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Models\PMM_DB.    cs:line 104
   at PMM.Models.PMM_DB.saveValidation(String PTLUniqueID, String ValidatedBy, DateTime ValidationDateTime, Nullable`1 ValidationCategoryID, String ValidationCategory,     String Comment, Nullable`1 ClockStartDate, Nullable`1 ClockStopDate, String StartRTTStatus, String StopRTTStatus, String LastRTTStatus, Boolean MergedPathway, String     MergedPathwayID, String ExtinctPathwayID, DataTable ChecklistResponses) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Models\PMM_DB.cs:line 265
   at PMM.Validate.lnkSaveButton_Click(Object sender, EventArgs e) in C:\WWW\nobulus\nobulusPMM\Application\PMM\Validate.aspx.cs:line 323
   at System.Web.UI.WebControls.LinkButton.OnClick(EventArgs e)
   at System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


Custom event details: 
like image 650
Adam Avatar asked Aug 04 '16 09:08

Adam


1 Answers

UPDATE

I actually found out using ILSpy and roaming in different framework classes that ASP.NET uses internally WebErrorEvent, which possess protected methods to achieve the same behavior.

Solution 1:

For that, just create a class which inherits WebErrorEvent, and then override its constructor:

public class CustomWebErrorEvent : WebErrorEvent
{
    public CustomWebErrorEvent(string message, EventSource source, int eventCode, Exception ex) : base(message, source, eventCode, ex)
    {
    }
}

Then using it inside the Error management method from Global.asax:

protected void Application_Error(Object sender, EventArgs e)
{
    // Log error to the Event Log
    Exception myError = null;
    if (HttpContext.Current.Server.GetLastError() != null)
    {
        var r = new CustomWebErrorEvent("error", null, 120, HttpContext.Current.Server.GetLastError());
    }
}

I am pretty sure it is posslbe also to overload ASPNET to only raise straight a custom WebErrorEvent, but I didn't find it yet.

I am still trying to figure out how to add custom info to the event as overriding the method FormatCustomEventDetails is not getting called for Web Managed error events.

Solution 2:

If not possible as missing adding custom fields for now, you can use a similar method that I wrote which does the same output:

// Log error to the Event Log
Exception myError = null;
if (HttpContext.Current.Server.GetLastError() != null)
{
    var request = HttpContext.Current.Request;
    myError = HttpContext.Current.Server.GetLastError();

    var dateAsBytes = System.Text.Encoding.UTF8.GetBytes(DateTime.Now.ToString("G"));
    var id = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(dateAsBytes));

    // Event info:
    var eventMessage = myError.Message;
    var currentTime = DateTime.Now.ToString("G");
    var currentTimeUTC = DateTime.UtcNow.ToString("G");

    // Application info:
    var appDomainName = AppDomain.CurrentDomain.FriendlyName;
    var appDomainTrustLevel = (AppDomain.CurrentDomain.IsFullyTrusted) ? "Full" : "Partial";
    var appVirtualPath = VirtualPathUtility.GetDirectory(request.Path);
    var appPath = request.PhysicalApplicationPath;
    var machineName = Environment.MachineName;

    // Process info:
    var process = Process.GetCurrentProcess();
    var processId = process.Id;
    var processName = process.ProcessName;
    var user = System.Security.Principal.WindowsIdentity.GetCurrent().User;
    var accountName = user.Translate(typeof(System.Security.Principal.NTAccount));

    // Exception info:
    var exceptionType = myError.GetType().FullName;
    var exceptionMessage = myError.Message;
    var exceptionStack = myError.StackTrace;

    // Request info:
    var url = request.Url.AbsoluteUri;
    var urlPath = request.Url.PathAndQuery;
    var remoteAddress = request.UserHostAddress;
    var userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
    var isAuthenticated = HttpContext.Current.User.Identity.IsAuthenticated;
    var authenticationType = System.Security.Principal.WindowsIdentity.GetCurrent().AuthenticationType;

    // Thread info:
    var impersonationLevel = System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel;
    var exceptionStack2 = myError.StackTrace;

    // TODO: aggregate all info as string before writting to EventLog.
}

I found using existing .NET Apis almost all required fields from your output, just needed know to be aggregated as a string before outputting it in EventLog.

You can see that some of the object I am using (such as AppDomain.CurrentDomain, HttpContext.Current.Request or Process.GetCurrentProcess() returns a lot of other info, which could also ne added to the output if needed.

This all can be wrapped up under a method for code brevity of course.

like image 177
Benjamin Soulier Avatar answered Nov 15 '22 00:11

Benjamin Soulier