MVC Custom error pages (log to database with Log4Net and notify by email)
There is a lot articles in web how to create and use custom error pages. I tried to implement one more solution and give you my example how to handle and log errors on website using Log4Net library and notify about error by email.

Browse to bin folder of your solution and select log4net.dll:

Run the following script to create the table that Log4Net will log message to:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[Log4Net_Error](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Date] [datetime] NOT NULL,
	[Thread] [varchar](255) NOT NULL,
	[Level] [varchar](50) NOT NULL,
	[Logger] [varchar](255) NOT NULL,
	[Message] [varchar](4000) NOT NULL,
	[Exception] [varchar](2000) NULL
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

Now we should configure Log4Net in web.config file. Add next lines right after <configuration> node (<configSections> should be first sub node of <configuration> node):

<configSections>
	<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>

Now underneath of web.config file (but still inside <configuration> node):

<log4net>
	<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
		<bufferSize value="1" />
		<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
		<connectionString value="data source=servername;Initial Catalog=databasename;Persist Security Info=True;User ID=userid;Password=password" />
		<commandText value="INSERT INTO Log4Net_Error ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
		<parameter>
			<parameterName value="@log_date" />
			<dbType value="DateTime" />
			<layout type="log4net.Layout.RawTimeStampLayout" />
		</parameter>
		<parameter>
			<parameterName value="@thread" />
			<dbType value="String" />
			<size value="255" />
			<layout type="log4net.Layout.PatternLayout">
				<conversionPattern value="%thread" />
			</layout>
		</parameter>
		<parameter>
			<parameterName value="@log_level" />
			<dbType value="String" />
			<size value="50" />
			<layout type="log4net.Layout.PatternLayout">
				<conversionPattern value="%level" />
			</layout>
		</parameter>
		<parameter>
			<parameterName value="@logger" />
			<dbType value="String" />
			<size value="255" />
			<layout type="log4net.Layout.PatternLayout">
				<conversionPattern value="%logger" />
			</layout>
		</parameter>
		<parameter>
			<parameterName value="@message" />
			<dbType value="String" />
			<size value="4000" />
			<layout type="log4net.Layout.PatternLayout">
				<conversionPattern value="%message" />
			</layout>
		</parameter>
		<parameter>
			<parameterName value="@exception" />
			<dbType value="String" />
			<size value="2000" />
			<layout type="log4net.Layout.ExceptionLayout" />
		</parameter>
	</appender>

	<!-- Set root logger level to DEBUG and its only appender to A1 -->
	<root>
		<level value="DEBUG" />
		<appender-ref ref="AdoNetAppender" />
	</root>
</log4net>

Don't forget to configure connection string to your database <connectionString value="data source=servername;Initial Catalog=databasename;Persist Security Info=True;User ID=userid;Password=password" />
Step 3: Create Error Controller and views which will be shown on error throw:

// ErrorController.cs
using System;
using System.Web.Mvc;

namespace Kitsula.Controllers
{
    public class ErrorController : Controller
    {
        public ActionResult Index(string errorCode)
        {
            if (!string.IsNullOrEmpty(errorCode) && errorCode.Equals("404"))
            {
                Response.StatusCode = 404;
                return View("404");
            }
            return View();
        }
    }
}

In my case I have two views: first is for all errors, second – for 404 errors. Register route for these views:

// Global.asax.cs
routes.MapRoute(
	"Error", // Route name
	"Error/{errorCode}",
	new { controller = "Error", action = "Index", errorCode = (string)null }, // Parameter defaults
	new string[] { "Kitsula.Controllers" }
);

Now everything are prepared for catching and logging errors.
Step 4: Catching error in application, creating and sending email with error message, loging it to DB using Log4Net, showing to user friendly page about error. To catch errors I suggest use Application_Error event. In this case we can catch almost all errors of whole app. Let’s write our behavior for the event in Global.asax.cs:

//Global.asax.cs

private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

public void Application_Error(object sender, EventArgs e)
{
    // Init Log4Net library
    log4net.Config.XmlConfigurator.Configure();
    
    try
    {
        // Get Error object
        Exception objErr = Server.GetLastError().GetBaseException();
        string sitestr = Request.Url.ToString();
        // Build email
        System.Text.StringBuilder bodystr = new System.Text.StringBuilder();
        bodystr.Append("<html><head>");

        bodystr.Append("<style>");
        bodystr.Append(" body {font-family:\"Verdana\";font-weight:normal;font-size: .7em;color:black;} ");
        bodystr.Append(" p {font-family:\"Verdana\";font-weight:normal;color:black;margin-top: -5px}");
        bodystr.Append(" b {font-family:\"Verdana\";font-weight:bold;color:black;margin-top: -5px}");
        bodystr.Append(" H1 { font-family:\"Verdana\";font-weight:normal;font-size:16pt;color:red }");
        bodystr.Append(" H2 { font-family:\"Verdana\";font-weight:normal;font-size:14pt;color:maroon }");
        bodystr.Append(" H4 { font-family:\"Verdana\";font-weight:bold;font-size:11pt;}");
        bodystr.Append(" pre {font-family:\"Lucida Console\";font-size: .9em}");
        bodystr.Append(" .marker {font-weight: bold; color: black;text-decoration: none;}");
        bodystr.Append(" .version {color: gray;}");
        bodystr.Append(" .error {margin-bottom: 10px;}");
        bodystr.Append(" .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }");
        bodystr.Append("</style>");
        bodystr.Append("</head><body bgcolor=\"white\">");
        
        bodystr.Append(String.Format("<b>Date:</b> {0}<br>", DateTime.Now));
        bodystr.Append(String.Format("<b>Page:</b> <a href=\"{0}\">{0}</a><br><br>", sitestr));

        bodystr.Append(String.Format("<h1>Error in \"{0}\" application</h1>", objErr.Source));
        bodystr.Append("<font face=\"Arial, Helvetica, Geneva, SunSans-Regular, sans-serif \">");
        bodystr.Append(String.Format("<b>Error Type:</b> {0}<br>", objErr.GetType()));
        bodystr.Append(String.Format("<b>Error Message:</b> {0}<br>", objErr.Message));
        bodystr.Append("<b>Stack Trace:</b> <br><br>");
        bodystr.Append(" <table width=100% bgcolor=\"#ffffcc\">");
        bodystr.Append("  <tr>");
        bodystr.Append("   <td>");
        bodystr.Append("    <code><pre>");
        bodystr.Append(objErr.StackTrace);
        bodystr.Append("</pre></code>");
        bodystr.Append("   </td>");
        bodystr.Append(" </tr>");
        bodystr.Append("</table>");
        bodystr.Append("<br>");
        bodystr.Append("<hr width=100% size=1 color=silver>");
        bodystr.Append("<b>Version Information:</b> " + Environment.Version.ToString());
        bodystr.Append("</font>");

        int i = 0;
        bodystr.Append("<h4>Query Strings:</h4>");
        for (i = 0; i <= Context.Request.QueryString.Count - 1; i++)
        {
            bodystr.Append(String.Format("<b>{0}:</b> {1}<br>", Context.Request.QueryString.Keys[i], Context.Request.QueryString[i]));
        }
        bodystr.Append("<h4>Form:</h4>");
        for (i = 0; i <= Context.Request.Form.Count - 1; i++)
        {
            bodystr.Append(String.Format("<b>{0}:</b> {1}<br>", Context.Request.Form.Keys[i], Context.Request.Form[i]));
        }

        bodystr.Append("<h4>Server Variables:</h4>");
        for (i = 0; i <= Context.Request.ServerVariables.Count - 1; i++)
        {
            bodystr.Append(String.Format("<b>{0}:</b> {1}<br>", Context.Request.ServerVariables.Keys[i], Context.Request.ServerVariables[i]));
        }

        // Add Session Values in email
        bodystr.Append("<h4>Session Variables:</h4>");

        if ((Context.Session != null))
        {
            if (Context.Session.Count > 0)
            {
                foreach (string item in Context.Session.Keys)
                {
                    bodystr.Append(String.Format("<b>{0}:</b> {1}<br>", item, (Context.Session[item] == null ? "" : Context.Session[item].ToString())));
                }
            }
            else
            {
                bodystr.Append("No Session values: 0 <br>");
            }
        }
        else
        {
            bodystr.Append("No Session values: nothing <br>");
        }
        bodystr.Append("</body></html>");

        // Send email with error description, session variables, etc.
        AlternateView bodyHtmlView = AlternateView.CreateAlternateViewFromString(bodystr.ToString(), new System.Net.Mime.ContentType("text/html"));
        using (MailMessage message = new MailMessage { From = new MailAddress("support@kitsula.com") })
        {
            message.To.Add(new MailAddress("support@kitsula.com"));
            message.Bcc.Add(new MailAddress("IgorKitsula@gmail.com"));
            message.Subject = String.Format("Error on the site - {0}", objErr.GetType());
            message.AlternateViews.Add(bodyHtmlView);
            using (SmtpClient client = new SmtpClient())
            {
                client.Send(message);
            }
        }
        // Save error to database using Log4Net
        log.Error(bodystr.ToString(), objErr);
    }
    catch (Exception ex)
    {
        // If sending email failed - save error to DB using Log4Net
        log.Fatal("Error", ex);
    }

    // Show custom error pages
    if ((Context.Server.GetLastError() is HttpException) && ((Context.Server.GetLastError() as HttpException).GetHttpCode() != 404))
    {
        Server.ClearError();
        Response.Redirect("/Error", false);
    }
    else
    {
        // Handle 404 error and response code
        Server.ClearError();
        Response.Redirect("/Error/404", false);
    }
}

That’s it. :) As result: we have 2 custom error pages http://kitsula.com/Error and http://kitsula.com/Error/404 email:

Sources: