Journal Class and Logging - ASP.NET

Table of Contents [Hide/Show]


   Overview
   Sample Implementation
         VB.NET
         C#
   Source Code
      Journal Table
      InsertJournalEntry Stored Procedure
      Journal Class
         VB.NET
         C#
      Error.aspx
      Application_Error Event Handler
         VB.NET
         C#

{outline||<1> - |

.<1> - }

Overview



Sample Implementation

1. Create the Journal table in the database. If appropriate for your implementation, change the data type for the UserId field to varchar.

2. Create the stored procedure dbo.InsertJournalEntry. If appropriate for your implementation, change the data type for the UserId field to varchar.

3. Create the Journal class

4. Create the Error.aspx page

5. Create/edit the Global.asax page

6. Add exception handling to all procedures, and include the following line before the throw ex line.

VB.NET

{copytext|ErrorHandlerVb}
Journal.Write(ex) : HttpContext.Current.Response.End()


C#

{copytext|ErrorHandlerCs}
Journal.Write(ex); HttpContext.Current.Response.End();


Source Code

Journal Table

{copytext|JournalTable}
CREATE TABLE dbo.Journal(
    JournalId  int IDENTITY(1,1)  NOT NULL,
    LoggedAt   datetime           NOT NULL  CONSTRAINT DF_Journal_LoggedAt  DEFAULT (getutcdate()),
    Severity   tinyint            NOT NULL,
    Message    varchar(1000)      NOT NULL,
    Detail     varchar(1000)      NOT NULL,
    ModName    varchar(300)       NOT NULL,
    ProcName   varchar(300)       NOT NULL,
    LineNum    int                NOT NULL,
    RemoteIP   varchar(15)        NOT NULL,
    UserId     uniqueidentifier   NULL,
    FullUrl    varchar(1000)      NOT NULL,
    StackTrace varchar(max)       NOT NULL,
 CONSTRAINT PK_Journal PRIMARY KEY CLUSTERED 
(
    JournalId ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

InsertJournalEntry Stored Procedure

{copytext|InsertJournalEntrySp}
create procedure dbo.InsertJournalEntry
    (
     @Severity      tinyint
    ,@Message       varchar(1000)
    ,@Detail        varchar(1000)
    ,@StackTrace    varchar(max)
    ,@ModName       varchar(300)
    ,@ProcName      varchar(300)
    ,@LineNum       int
    ,@RemoteIP      varchar(15)
    ,@UserId        uniqueidentifier
    ,@FullUrl       varchar(1000)
    ) as

INSERT INTO dbo.Journal (
     LoggedAt
    ,Severity
    ,Message
    ,Detail
    ,StackTrace
    ,ModName
    ,ProcName
    ,LineNum
    ,RemoteIP
    ,UserId
    ,FullUrl
    )
select
     LoggedAt   = getutcdate()
    ,Severity   = @Severity
    ,Message    = @Message
    ,Detail     = @Detail
    ,StackTrace = @StackTrace
    ,ModName    = @ModName
    ,ProcName   = @ProcName
    ,LineNum    = @LineNum
    ,RemoteIP   = @RemoteIP
    ,UserId     = @UserId
    ,FullUrl    = @FullUrl

Journal Class

VB.NET

{copytext|JournalVb}
Imports Microsoft.VisualBasic
Imports System.Diagnostics
Imports System.Reflection
Imports System.Data
Imports System.Data.SqlClient

Public Class Journal
Inherits SqlDatabaseWeb

    Private _dbName As String = "RequestTracker"

    Public Shared Sub Write(ByVal exception As Exception, Optional ByVal detail As String = "", _
    Optional ByVal userId As String = "")

        ' Ignore ThreadAbortExceptions because they're caused by Response.Redirect() and Response.End()
        If Not (TypeOf (exception) Is System.Threading.ThreadAbortException) Then

            Dim msg As String = ""

            Try
                msg = "Couldn't initialize variables"
                Dim severity As Byte = 1 ' 1 = error
                Dim message As String = exception.Message
                Dim stackTrace As String = exception.StackTrace
                Dim lineNum As Integer = GetLineNumber(stackTrace)
                Dim modName As String = ""
                Dim procName As String = ""
                Dim request As HttpRequest = HttpContext.Current.Request
                Dim remoteIp As String = request.UserHostAddress
                Dim fullUrl As String = request.RawUrl
                Dim sf As StackFrame = New StackFrame(1, True)

                If sf IsNot Nothing Then

                    msg = "Couldn't get method base"
                    Dim mb As MethodBase = sf.GetMethod()

                    If mb IsNot Nothing And mb.DeclaringType IsNot Nothing Then

                        msg = "Couldn't get modname"
                        modName = mb.DeclaringType.FullName

                        msg = "Couldn't get procname"
                        procName = mb.Name
                        If lineNum = 0 Then
                            msg = "Couldn't get line number"
                            lineNum = sf.GetFileLineNumber()
                        End If

                    End If

                End If

                msg = "Couldn't instantiate Journal"
                Dim log As Journal = New Journal()

                msg = "Couldn't insert journal entry"
                log.InsertJournalEntry(severity, message, detail, stackTrace, modName, procName, _
                            lineNum, remoteIp, userId, fullUrl)

                msg = "Couldn't redirect to Error.aspx"
                HttpContext.Current.Response.Redirect("Error.aspx", False)

                msg = "Couldn't end response"
                HttpContext.Current.Response.End()

            Catch ex As Exception

                Throw ex

            End Try

        End If

    End Sub

    Public Sub InsertJournalEntry(ByVal severity As Byte, ByVal message As String, ByVal detail _
    As String, ByVal stackTrace As String, ByVal modName As String, ByVal procName As String, _
    ByVal lineNum As Integer, ByVal remoteIp As String, ByVal userId As String, ByVal fullUrl As _
    String)

        Dim conn As SqlConnection = Nothing
        Dim cmd As SqlCommand = Nothing

        Try

            conn = MyBase.OpenConnection(_dbName)
            cmd = MyBase.PrepCommand(conn, "dbo.InsertJournalEntry")

            cmd.Parameters.AddWithValue("Severity", severity)
            cmd.Parameters.AddWithValue("Message", message)
            cmd.Parameters.AddWithValue("Detail", detail)
            cmd.Parameters.AddWithValue("StackTrace", stackTrace)
            cmd.Parameters.AddWithValue("ModName", modName)
            cmd.Parameters.AddWithValue("ProcName", procName)
            cmd.Parameters.AddWithValue("LineNum", lineNum)
            cmd.Parameters.AddWithValue("RemoteIp", remoteIp)

            If userId.Length = 0 Then
                cmd.Parameters.AddWithValue("UserId", DBNull.Value)
            Else
                cmd.Parameters.AddWithValue("UserId", userId)
            End If

            cmd.Parameters.AddWithValue("FullUrl", fullUrl)

            cmd.ExecuteNonQuery()

        Catch ex As Exception

            Throw ex

        Finally

            MyBase.CleanUp(conn, cmd)

        End Try

    End Sub

    Private Shared Function GetLineNumber(ByVal stackTrace As String) As Integer

        Dim result As Integer = 0
        Dim pos As Integer = stackTrace.LastIndexOf(" ")
        If pos > -1 Then
            Dim s As String = stackTrace.Substring(pos)
            If Not Integer.TryParse(s, result) Then
                result = 0
            End If
        End If
        Return result

    End Function

End Class

C#

{copytext|JournalCs}
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using LibSystem.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Reflection;

public class Journal : SqlDatabaseWeb
{
    private string _dbName = "SeinfeldDb";

    public static void Write(Exception exception, string detail, string userId)
    {
        if (!((exception) is System.Threading.ThreadAbortException))
        {
            try
            {
                byte severity = 1; // 1 = error
                string message = exception.Message;
                string stackTrace = exception.StackTrace; 
                int lineNum = GetLineNumber(stackTrace);
                string modName = "";
                string procName = "";
                HttpRequest request = HttpContext.Current.Request;
                string remoteIp = request.UserHostAddress;
                string fullUrl = request.RawUrl;
    
                StackFrame sf = new StackFrame(1, true);
                if (sf != null)
                {
                    MethodBase mb = sf.GetMethod();
    
                    if (mb != null && mb.DeclaringType != null)
                    {
                        modName = mb.DeclaringType.FullName;
                        procName = mb.Name;
                        if (lineNum == 0)
                            lineNum = sf.GetFileLineNumber();
                    }
                }
    
                Journal log = new Journal();
                log.InsertJournalEntry(severity, message, detail, stackTrace, modName, procName, lineNum, 
                    remoteIp, userId, fullUrl);
                HttpContext.Current.Response.Redirect("~/Error.aspx");
                HttpContext.Current.Response.End();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }

    public void InsertJournalEntry(byte severity, string message, string detail, string stackTrace, 
        string modName, string procName, int lineNum, string remoteIp, string userId, string fullUrl)
    {
        SqlConnection conn = null;
        SqlCommand cmd = null;

        try
        {
            conn = base.OpenConnection(_dbName);
            cmd = base.PrepCommand(conn, "dbo.InsertJournalEntry"); 

            cmd.Parameters.AddWithValue("Severity", severity);
            cmd.Parameters.AddWithValue("Message", message);
            cmd.Parameters.AddWithValue("Detail", detail);
            cmd.Parameters.AddWithValue("StackTrace", stackTrace);
            cmd.Parameters.AddWithValue("ModName", modName);
            cmd.Parameters.AddWithValue("ProcName", procName);
            cmd.Parameters.AddWithValue("LineNum", lineNum);
            cmd.Parameters.AddWithValue("RemoteIp", remoteIp);

            if (userId.Length == 0)
                cmd.Parameters.AddWithValue("UserId", DBNull.Value);
            else
                cmd.Parameters.AddWithValue("UserId", userId);

            cmd.Parameters.AddWithValue("FullUrl", fullUrl);

            cmd.ExecuteNonQuery();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            base.CleanUp(ref conn, ref cmd);
        }
    }

    private static int GetLineNumber(string stackTrace)
    {
        int result = 0;
        int pos = stackTrace.LastIndexOf(' ');

        if (pos > -1)
        {
            string s = stackTrace.Substring(pos);
            if (!int.TryParse(s, out result))
                result = 0;
        }

        return result;
    }
}

Error.aspx

{copytext|ErrorAspx}
<%@ Page Title="Error" %>

<html>
<body>
    <asp:Label runat="server" Font-Size="Medium" ForeColor="red" Font-Bold="true" Text="Error" />
    <br />
    <asp:Label runat="server">An error has occured. This error has been logged, and the webmaster 
    has been notified.  If you were saving data, your changes were most likely lost, and you will 
    need to try again.</asp:Label>
</body>
</html>

Application_Error Event Handler

Include the following code in your Global.asax file.

VB.NET

{copytext|ApplicationErrorVb}
Sub Application_Error(ByVal sender as Object, ByVal e As EventArgs)

    ' Code that runs when an unhandled error occurs
    Journal.Write(Server.GetLastError(), "", "")

End Sub

C#

{copytext|ApplicationErrorCs}
void Application_Error(object sender, EventArgs e) 
{ 
    // Code that runs when an unhandled error occurs
    Journal.Write(Server.GetLastError(), "", "");
}