A while ago I wrote a short article about how to handle and report errors in your scripts. Well, here I am again, but this time I'm writing about IIS 5.0 and hopefully in a way that allows you to implement it a little easier.
Internet Information Services 5.0 (IIS) is the Windows 2000 Web service that makes it easy to publish information on your intranet for the Internet.
As you will know if you run IIS 5.0, it has a feature that allows you to configure how 500 internal server errors are displayed. IIS 5.0 doesn't do such a bad job in its standard configuration, mainly because of the new, enhanced error object and that the default reporting method displays some useful information.
Now we've all been in the situation where we've written code and put it out for user testing. I don't know about you, but I'm certainly down a few hair follicles from having tried to extract information that will help me from a user who has found an error. So, I set about writing this script in a vain attempt to prevent premature baldness.
What I have written a script that allows you to choose how you want errors handled and reported. In short, this script allows you to choose from the following:
1. To display or not display an error message containing (any or all of) querystring, form, session, or application variables values to the user from the page that caused the error.
2. To log to a database and/or text file.
3. To send an email to someone containing all of the above, together with a note confirming the success or failure, or the database or text file logging procedure.
All the variable names are very descriptive so you can read them yourself. If you would like to cut straight to the code, you can download the entire file here:
Having first included the file containing all the constants, ado15.dll, we want to define all the Boolean values that will execute the prebuilt functions, as follows:
Next, we define the variables that we want to have page-level scope:
Dim strServerName
Dim strServerPort
Dim strScriptName
Dim strURL
Dim strScriptLink
Dim strCRLF 'You could also use vbCRLF
Dim strSectionDelimiter
Dim strDelimiter
Dim strQSEmailValues
Dim strFormEmailValues
Dim strSessionEmailValues
Dim strApplicationEmailValues
Dim strErrorHandlerConfig
Dim blnAppendLogSuccessfully
Dim blnAppendDatabaseSuccessfully
Dim strErrDesc
We want to assign some values to variables for use on this page:
strServerName = Request.ServerVariables("Server_Name")
strServerPort = Request.ServerVariables("Server_Port")
strScriptName = Request.ServerVariables("Script_Name")
strURL = "http://" & strServerName & ":" & strServerPort & "/" & strScriptName
strScriptLink = "<a href=" & strURL & ">" & strURL & "</a>"
strCRLF = Chr(13) & Chr(10) 'Create the paragraph break
strSectionDelimiter = "#########################################" & strCRLF
strDelimiter = "|" 'delimiter for use in the error details string for
'appending to the log
The purpose of these is to build a link for use on an email and to establish some formatting for the email to make it more readable.
We now need to access that details of the error contained in the error object:
We have set up the Boolean and required values we need to define what will happen in some functions.
Function ShowUserTheErrorDetails()%>
<P><font face="Arial, Helvetica, sans serif" size="3">An Error of the type <B>"<%=strCategory%>"</b> has occurred</font></P>
<P><font face="Arial, Helvetica, sans serif" size="2">There is no need to contact us about this error as we are already aware.
This is for information only. <BR>Regards<BR>The <%=strServerName%> Web Master</font></P>
<table border="1" bordercolorlight="#000000" bordercolordark="#ffffff" bgcolor="#cccccc" cellpadding="0" cellspacing="0">
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Error Number</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strErrorNo <> "" then
Response.Write strErrorNo
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Error Code</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strErrorCode <> "" then
Response.Write strErrorCode
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Error Description</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strErrorDescription <> "" then
Response.Write strErrorDescription
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">ASP Description</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strASPDescription <> "" then
Response.Write strASPDescription
else
Response.Write " "
end if
%></font></td>
</tr>
<TR>
<TD><font face="Arial, Helvetica, sans serif" size="1">Category</FONT></TD>
<TD><font face="Arial, Helvetica, sans serif" size="1"><%
if strCategory <> "" then
Response.Write strCategory
else
Response.Write " "
end if
%></font></TD></TR>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">File Name</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strFileName <> "" then
Response.Write strFileName
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Line Number</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strLineNo <> "" then
Response.Write strLineNo
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Column</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strCol <> "" then
Response.Write strCol
else
Response.Write " "
end if
%></font></td>
</tr>
<tr>
<td><b><font face="Arial, Helvetica, sans serif" size="1">Error Source</font></b></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><%
if strErrorSource <> "" then
Response.Write strErrorSource
else
Response.Write " "
end if
%></font></td>
</tr>
</table>
<%
End Function
Function CaptureQSValuesForEmail()
if Request.QueryString <> "" then ' loop through all the querystring values and write them to the email
strQSEmailValues = strCRLF & strSectionDelimiter
strQSEmailValues = "QUERYSTRING VALUES (if any) FOLLOW: " & strCRLF & strCRLF
For each x in Request.QueryString
strQSEmailValues = strQSEmailValues & x & " : " & Request.QueryString(x) & strCRLF
next
strQSEmailValues = strQSEmailValues & strCRLF & strCRLF
strQSEmailValues = strQSEmailValues & "The full URL is: " & strURL & "?" & Request.QueryString
end if
End Function
Function ShowUserTheQSValues()%>
<P><font face="Arial, Helvetica, sans serif" size="2"><B>The querystring values (if any) are:</b></font></P>
<table border="1" bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#CCCCCC" cellpadding="0" cellspacing="0">
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Name</b></font></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Value</b></font></td>
</tr>
<%
if Request.QueryString <> "" then ' loop through all the querystring values and write them to the email
For each x in Request.QueryString%>
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><%=x%></font></td>
<td>
<%if Request.QueryString(x) <> "" then%>
<font face="Arial, Helvetica, sans serif" size="1"><%=Request.QueryString(x)%></font>
<%else%>
<%end if%></td>
</tr>
<%
next
end if
%>
</table>
<%
End Function
Function CaptureFormValuesForEmail()
if Request.Form <> "" then ' loop through all the form values and write them to the email
strFormEmailValues = strCRLF & strSectionDelimiter
strFormEmailValues = strFormEmailValues & "FORM VALUES (if any) FOLLOW: " & strCRLF & strCRLF
For each x in Request.Form
strFormEmailValues = strFormEmailValues & x & " : " & Request.Form(x) & strCRLF
next
end if
End Function
Function ShowUserTheFormValues()%>
<P><font face="Arial, Helvetica, sans serif" size="2"><B>The form values (if any) are:</b></font></P>
<table border="1" bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#CCCCCC" cellpadding="0" cellspacing="0">
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Name</b></font></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Value</b></font></td>
</tr>
<%
if Request.Form <> "" then ' loop through all the querystring values and write them to the email
For each x in Request.Form%>
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><%=x%></font></td>
<td>
<%if Request.Form(x) <> "" then%>
<font face="Arial, Helvetica, sans serif" size="1"><%=Request.Form(x)%></font>
<%else%>
<%end if%></td>
</tr>
<%
next
end if
%>
</table>
<%
End Function
Function CaptureSessionValuesForEmail()
' loop through all the session variable values and write them to the email
strSessionEmailValues = strCRLF & strSectionDelimiter
strSessionEmailValues = strSessionEmailValues & "SESSION VARIABLE VALUES (if any) FOLLOW: " & strCRLF & strCRLF
For Each sessitem in Session.Contents
strSessionEmailValues = strSessionEmailValues & sessitem & " : " & Session.Contents(sessitem) & strCRLF
Next
End Function
Function ShowUserTheSessionValues()%>
<P><font face="Arial, Helvetica, sans serif" size="2"><B>The session variable values (if any) are:</b></font></P>
<table border="1" bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#CCCCCC" cellpadding="0" cellspacing="0">
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Name</b></font></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Value</b></font></td>
</tr>
<%
For each sessitem in Session.Contents%>
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><%=sessitem%></font></td>
<td>
<%if Session.Contents(sessitem) <> "" then%>
<font face="Arial, Helvetica, sans serif" size="1"><%=Session.Contents(sessitem)%></font>
<%else%>
<%end if%></td>
</tr>
<%
next
%>
</table>
<%
End Function
Function CaptureApplicationValuesForEmail()
' loop through all the session variable values and write them to the email
strApplicationEmailValues = strCRLF & strSectionDelimiter
strApplicationEmailValues = strApplicationEmailValues & "APPLICATION VARIABLE VALUES (if any) FOLLOW: " & strCRLF & strCRLF
For Each appitem in Application.Contents
strApplicationEmailValues = strApplicationEmailValues & appitem & " : " & Application.Contents(appitem) & strCRLF
Next
End Function
Function ShowUserTheApplicationValues()%>
<P><font face="Arial, Helvetica, sans serif" size="2"><B>The Application variable values (if any) are:</b></font></P>
<table border="1" bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#CCCCCC" cellpadding="0" cellspacing="0">
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Name</b></font></td>
<td><font face="Arial, Helvetica, sans serif" size="1"><b>Value</b></font></td>
</tr>
<%
For each appitem in Application.Contents%>
<tr>
<td><font face="Arial, Helvetica, sans serif" size="1"><%=appitem%></font></td>
<td>
<%if Application.Contents(appitem) <> "" then%>
<font face="Arial, Helvetica, sans serif" size="1"><%=Application.Contents(appitem)%></font>
<%else%>
<%end if%></td>
</tr>
<%
next
%>
</table>
<%
End Function
Function ConfigurationReminder()
'build an error handler configuration string for display at the foot of the email to remind us how we have configured it
strErrorHandlerConfig = strCRLF & strSectionDelimiter
strErrorHandlerConfig = strErrorHandlerConfig & "****Error Handler Configuration Details****" & strCRLF 'section title
strErrorHandlerConfig = strErrorHandlerConfig & " **Show Client Error Details = "
if blnShowUserTheError = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Show User The QueryString Values = "
if blnShowUsertheQSValues = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Capture QueryString Values = "
if blnCaptureQSValuesForEmail = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Show User The Form Values = "
if blnShowUserTheFormValues = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Capture Form Values = "
if blnCaptureFormValuesForEmail = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Show User The Session Variable Values = "
if blnShowUserTheSessionValues = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Capture Application Variable Values = "
if blnCaptureApplicationValuesForEmail = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Show User The Application Variable Values = "
if blnShowUserTheApplicationValues = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Capture Session Variable Values = "
if blnCaptureSessionValuesForEmail = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Send Administrative Email = "
if blnSendAdminMail = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes" & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Append Error Log = "
if blnAppendLog = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes, succeeded=" & blnAppendLogSuccessfully
if blnAppendLogSuccessfully = False then
strErrorHandlerConfig = strErrorHandlerConfig & ", reason: " & strErrDesc & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & strCRLF
end if
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
strErrorHandlerConfig = strErrorHandlerConfig & " **Append Database = "
if blnAppendDatabase = True then
strErrorHandlerConfig = strErrorHandlerConfig & "yes, succeeded=" & blnAppendDatabaseSuccessfully
if blnAppendLogSuccessfully = False then
strErrorHandlerConfig = strErrorHandlerConfig & ", reason: " & strErrDesc & strCRLF
else
strErrorHandlerConfig = strErrorHandlerConfig & strCRLF
end if
else
strErrorHandlerConfig = strErrorHandlerConfig & "no" & strCRLF
end if
End Function
Function AppendErrorLog()
On Error Resume Next 'we need this because if we error out here the script stops
'build error string for appending to log
strErrorDetails = strErrorNo & strDelimiter & strErrorCode & strDelimiter & strErrorDescription & strDelimiter & strASPDescription & strDelimiter
& strCategory & strDelimiter & strFileName & strDelimiter & strLineNo & strDelimiter & strCol & strDelimiter & strErrorSource
'set up variables
strPathOfFile = "C: emp" 'you must add the IUSR_YourMachineName account to the permissions for your chosen directory (backslash is
required)
strNameOfFile = "scripterrorlog.txt"
Set objFileSystem = Server.CreateObject("Scripting.FileSystemObject")
Set objTextStream = objFileSystem.OpenTextFile(strPathOfFile & strNameOfFile, 8, True)
if Err.number = 0 then objTextStream.WriteLine strErrorDetails
if Err.number = 0 then
objTextStream.Close
blnAppendLogSuccessfully = True 'flag to report in admin email how things went
else
blnAppendLogSuccessfully = False 'flag to report in admin email how things went
strErrDesc = Err.description
objTextStream.Close
end if
On Error GoTo 0 'restore default error checking
End Function
Function AppendDatabase()
strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source= yourdatabasepath;" 'database connectionstring
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open strConnectionString
Set rsAddNewRow = Server.CreateObject("ADODB.Recordset")
rsAddNewRow.Open "tbl_ErrorLogs", objConn, adOpenDynamic, adLockOptimistic, adCmdTableDirect
With rsAddNewRow
.AddNew
.Fields("ErrorNo") = strErrorNo
.Fields("ErrorCode") = strErrorCode
.Fields("ErrorDescription") = strErrorDescription
.Fields("ASPDescription") = strASPDescription
.Fields("Category") = strCategory
.Fields("FileName") = strFileName
.Fields("LineNo") = strLineNo
.Fields("Col") = strCol
.Fields("ErrorSource") = strErrorSource
.Update
End With
rsAddNewRow.Close
objConn.Close
Set rsAddNewRow = Nothing
Set objConn = Nothing
End Function
Function SendAdminEmail()
'define variables
strFromName = "Script Error Function" 'Email from name
strFromAddress = "your@fromname.com"
strRemoteHost = "your_outgoing_smtp_server"
strSubjectText = "Error Occurred on: " & strServerName
'create the first part of the body of the email
strBodyText = "An error of type """ & strCategory & """ has occurred at the web site: " & strServerName _
& strCRLF _
& strCRLF _
& "The URL of the error was : " & strURL _
& strCRLF _
& strCRLF _
& "The error details are as follows:" _
& strCRLF _
& "Error Number : " & strErrorNo _
& strCRLF _
& "Error Code : " & strErrorCode _
& strCRLF _
& "Error Description : " & strErrorDescription _
& strCRLF _
& "ASP Description : " & strASPDescription _
& strCRLF _
& "Category : " & strCategory _
& strCRLF _
& "File Name : " & strFileName _
& strCRLF _
& "Line Number : " & strLineNo _
& strCRLF _
& "Column : " & strCol _
& strCRLF _
& "Source : " & strErrorSource _
& strCRLF _
& strSectionDelimiter & strCRLF
' attempt to send the mail and notify the client if it fails - if it 'doesn't then notify that the webmaster has been notified - you will need to
'change this code if you do not use ASPMail
Set theMail = Server.CreateObject("SMTPsvg.Mailer")
theMail.AddRecipient "", strFromAddress
theMail.RemoteHost = strRemoteHost
theMail.FromAddress = strFromAddress
theMail.FromName = strFromName
theMail.Subject = strSubjectText
theMail.BodyText = strBodyText & strQSEmailValues & strFormEmailValues & strSessionEmailValues & strApplicationEmailValues &
strErrorHandlerConfig
if theMail.SendMail = false then
if theMail.Response <> "" then
strError = theMail.Response
else
strError = "Unknown"
end if
strMsg = "We tried to notify mark@mark but a program failure occurred. 'Please <a href=""mailto:mark@mark?subject=Error on this URL: "
& strServerPage & """> contact support</a> with following information. <BR><BR> Reason for
failure: " & strError & "<BR>"
Response.Write strMsg
end if
End Function
Since we have defined the Boolean values, variables, and built functions, we need to make the functions do their stuff if the related Boolean value is "True."
if blnShowUserTheError = True then
ShowUserTheErrorDetails()
end if
if blnCaptureQSValuesForEmail = True then
CaptureQSValuesForEmail()
end if
if blnShowUserTheQSValues = True then
ShowUserTheQSValues()
end if
if blnCaptureFormValuesForEmail = True then
CaptureFormValuesForEmail()
end if
if blnShowUserTheFormValues = True then
ShowUserTheFormValues()
end if
if blnCaptureSessionValuesForEmail = True then
CaptureSessionValuesForEmail()
end if
if blnShowUserTheSessionValues = True then
ShowUserTheSessionValues()
end if
if blnCaptureApplicationValuesForEmail = True then
CaptureApplicationValuesForEmail()
end if
if blnShowUserTheApplicationValues = True then
ShowUserTheApplicationValues()
end if
if blnAppendLog = True then
AppendErrorLog()
end if
if blnAppendDatabase = True then
AppendDatabase()
end if
if blnSendAdminMail = True then
ConfigurationReminder()
SendAdminEmail()
end if
Well, once you have all this, simply reconfigure IIS 5.0's properties, specifically the Custom Error tab, to point at the new file you have just created. Thereafter, you will know about the errors as soon as your user does. You can get the source code direct from http://www.activewebsystems.co.uk/source.asp.
About the Author
Mark Newlands has been working with the Web since 1994 and with ASP since it first came out. He set up shop about 18 months ago as a specialist in NT based Web sites and has developed many Web sites, as well as several e-commerce applications. He has now branched out on his own as an independent contractor and is specializing in transactional Web sites using ASP3.0, MSMQ and RDS. He may be reached at mark@activewebsystems.co.uk.
When errors occur on a Web site, they should be handled in a way that helps the user to get back on track. Unfortunately, setting up customized error pages in IIS usually requires something many Web developers lack -- access to and familiarity with the Web server's administrative interface. With CustomError for IIS, developers can add error pages, coded by hand or created in their favorite editor, by simply uploading them to a designated directory. No administrator intervention is required.
Automatic daily builds is a well known software engineering best practice. This article introduces a strategy for implementing and promoting daily builds and offers tips and tricks for preventing and fixing breaks. [Read This Article][Top]
Building an application can be more than pressing F5. With an increasing
number of quality packages being released, developers for the .NET platform now have options to create a very sophisticated build process. Aaron Junod describes a sample build environment and shows how a number of tools can work together to make reliable, predictable, and value-added builds. [Read This Article][Top]
This short article provides source code for a classic ASP online database functions testing application and shows how to configure and use the tool for either SQL Server or Oracle. [Read This Article][Top]
One of many improvements ASP.NET brings to the development table is in error handling. Adam Tuliper whips up a simple ASP.NET solution for handling those pesky and unexpected post-production errors. [Read This Article][Top]
Mansoor Ahmed Siddiqui explains debugging and tracing and shows how to create custom
trace listeners to help ensure hassle-free development. [Read This Article][Top]
Firing events on a Web server is an easy task. However most of the easy solutions require you to have your own dedicated IIS or SQL Server on the Internet to play with, a privilege not shared by many. In this article, Matthew Muller shows you how to get the same functionality in a shared hosting environment.
[Read This Article][Top]
Unlike programming inside a complete VB system, when using ByRef with ASP and COM, a complication arises because ASP's VBScript is not typed, but the component's VB is typed. This article will briefly explain how ByRef can be used with ASP and COM. [Read This Article][Top]
Transact-SQL provides developers with several database error-handling methods. Use these functions to efficiently handle database errors and add an extra level of data validation. This article discusses the @@ERROR, SP_ADDMESSAGE, and RAISERROR functions and provides examples on how to implement them.
[Read This Article][Top]
Read what advice members of the 15Seconds Discussion list had to offer on forcing pages to refesh, even when the user hits the back button.
[Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.