设计一个错误处理类库

上次发了“写一个监测跟踪的类库”的帖子,这次贴错误处理的类库

上次的帖子地址如下

http://onlytiancai.cnblogs.com/archive/2005/07/30/203649.html

其实写这些文章没啥用,就是开拓开拓思路而已,微软已经发布了几个好用的应用程序快,什么blok,我也记不住那些英文。把偶用的错误处理类库贴出来,是让大家批评批评看看有哪些设计不当,考虑不全的地方,大家多多指点一下,比直接用那些现成的东西有收获应该,对吧。

/*
 * Enumeration : GWExceptionMessages
 * Namespace   : GW.MonitorServices.Logging
 * Assembly    : GW.MonitorServices.Logging
 * Author       : Kevin Hoffman
 * Description : Enumeration containing index values into the string resource containing the standardized error messages
 
*/


using System;

namespace GW.MonitorServices.Logging
{
    
/// <summary>
    
/// 这是个保存错误类型的枚举,和ErrorMessages.resx资源文件里的
    
/// 详细错误描述相对应
    
/// </summary>

    public enum GWExceptionMessages
    
{
        DefaultMessage 
= 1000,
        DbLogFailed 
= 1001,
        SeeDatabase 
= 1002,
        PersistenceFailure 
= 1003
    }

}


/*
 * Class       : FileErrorLog
 * Namespace   : GW.MonitorServices.Logging
 * Assembly    : GW.MonitorServices.Logging
 * Author       : Kevin Hoffman
 * Description : Class responsible for logging error information to HTML text files as backup/alternate to DB error logging.
 
*/


using System;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;

namespace GW.MonitorServices.Logging
{

    
/// <summary>
    
/// 把错误持久到文本文件的类,这个类用来把错误信息或指定信息记录在一个文本文件里,并
    
/// 使用HTML格式,方便你查看
    
/// </summary>

    public class FileErrorLog
    
{
        
/// <summary>
        
/// 设置一个读写锁,防治在操作日志文本文件的时候别人也使用,这样就不会出现并发问题
        
/// </summary>

        private static readonly System.Threading.ReaderWriterLock errorFileLock =
            
new System.Threading.ReaderWriterLock();

        
/// <summary>
        
/// 一个静态方法,用来把一个异常信息记录下来
        
/// </summary>
        
/// <param name="e"></param>

        public static void LogError( Exception e )
        
{
            LogError( e, 
string.Empty );
        }


        
/// <summary>
        
/// 通过错误消息,引发错误的原始异常来持久化一个错误,这里时挖掘错误详细信息的地方,
        
/// 这里调用了MonitorUtilities工具函数的一些功能获取发生错误的堆栈等,以及遍历原始异常的内部异常,
        
/// 取出引发异常的类,方法等来获取发生错误时足够多的信息,并把它持久化
        
/// </summary>
        
/// <param name="e"></param>
        
/// <param name="rootMessage"></param>

        public static void LogError( Exception e, string rootMessage )
        
{
            GWTrace.EnteringMethod( MethodBase.GetCurrentMethod() );
            rootMessage 
= ( rootMessage == null ? string.Empty : rootMessage.Trim() );
            Exception originalException 
= e;

            System.Text.StringBuilder buffer 
= new System.Text.StringBuilder(4096);
            
if ( rootMessage.Length > 0 )
                buffer.AppendFormat(
"<font color='RED' size=2><b>{0}</b></font><br/>",
                    rootMessage );
            
while (e != null )
            
{
                buffer.AppendFormat(
"<font color='RED' size=2><b>{0}</b></font><br/>", e.Message );
                
if (e.TargetSite != null )
                
{
                    buffer.Append(
"<font size=1>");
                    buffer.AppendFormat( 
"at method {0}.{1}()<br/>", e.TargetSite.DeclaringType.Name, e.TargetSite.Name );
                }

                e 
= e.InnerException;
            }


            e 
= originalException;
            buffer.Append(
"<br><font size=2 color=red><b>Stack Trace:</b></font><br/>\n");
            buffer.Append(
"<font size=1>\n");
            buffer.Append(MonitorUtilities.ExpandStackTrace(e).Replace(
"\n""<br>\n"));
            buffer.Append(
"</font>");

            appendMessageToErrorLogFile( buffer.ToString() );
        }


        
/// <summary>
        
/// 通过错误信息,,错误参数来持久化一条消息,这个重载的方法通常用来记录一条人为的信息
        
/// 比如说谁在什么时候执行了一个什么操作,
        
/// </summary>
        
/// <param name="message"></param>
        
/// <param name="arrParams"></param>

        public static void LogError( string message, params Object[] arrParams )
        
{
            message 
= ( message == null ? string.Empty : message.Trim() );
            message 
= MonitorUtilities.SafeFormat( message, arrParams );
            appendMessageToErrorLogFile( message );
        }


        
/// <summary>
        
/// 这个是把错误状态持久化的真正的地方,使用System.IO下的几个类,把传进来的信息保存在文本文件里
        
/// 这里调用私有函数getAppErrorLogFileName()来获取错误日志文件的位置,下面的英文注释可能是说权限之类
        
/// 的问题吧,你设置的存放文本日志的地方要让给ASPNET用户有写的权限.因为这是个可能引发异常的方法,所以
        
/// 要做一些异常处理.System.IO里的几个类都比较熟悉了,就不在多注释了哦
        
/// </summary>
        
/// <param name="errorMessage"></param>

        private static void appendMessageToErrorLogFile( string errorMessage )
        
{
            
// Note that if the ASPNET account does not have write privileges
            
// in the directory that you are planning on storing the file-based
            
// error log, that file will never be created.
            System.IO.StreamWriter errLog = null;
            errorFileLock.AcquireWriterLock(
10000);
            
try
            
{
                errLog 
= System.IO.File.AppendText( getAppErrorLogFileName() );
                errLog.WriteLine(
"<span style='font-family:Verdana'><font size=2>");
                errLog.WriteLine(
"<br/><hr/>\n");
                errLog.WriteLine(
"The following error occurred in the application on {0}", System.DateTime.Now);
                errLog.WriteLine(
"<hr/>\n");
                errLog.WriteLine( errorMessage );
                errLog.Flush();
                errLog.Close();
            }

            
catch (Exception ex)
            
{
                GWTrace.Trace(
                    TraceLevel.Error, 
"An error occurred when saving an error to disk: {0}", ex.ToString() );
            }

            
finally
            
{
                errorFileLock.ReleaseLock();
                
try 
                
{
                    
if (errLog != null )
                    
{
                        errLog.Flush();
                        errLog.Close();
                    }

                }

                
catch { }
            }

        }


        
/// <summary>
        
/// 获取存放文本日志的位置,先从web.config里后去,如果设置的目录没有的话就创建一个目录,如果创建不成功的话
        
/// 就默认到C盘跟目录下写日志,一般系统最少有一个主分区,也就是说一般都会有个C盘,所以C盘很保险,这里也是考虑
        
/// 了多种情况,最终保证日志尽量能持久下来,这里是在指定的目录里,根据错误生成的时间来创建一个文本文件,以便
        
/// 以后方便分类按日期查看.这个方法里调用了私有静态方法checkAndCreateDir,接下来咱们就来看这个方法
        
/// </summary>
        
/// <returns></returns>

        private static string getAppErrorLogFileName()
        
{
            
string errorLogBaseDir = System.Configuration.ConfigurationSettings.AppSettings["ErrorLogBaseDir"];
            
string errorLogDir = checkAndCreateDir( errorLogBaseDir, "C:\\" );
            
            
string errorFileName = string.Format(
                
"Error-{0}.htm",
                System.DateTime.Now.ToString(
"yyyyMMMdd"));

            
return errorLogDir + errorFileName;
        }


        
/// <summary>
        
/// 从web.config里获取保存错误日志的目录,如果没有就创建,如果创建不了就返回一个默认的目录
        
/// </summary>
        
/// <param name="newDir">要检查的目录</param>
        
/// <param name="fallBackDir">默认目录</param>
        
/// <returns>最终返回的目录</returns>

        private static string checkAndCreateDir( string newDir, string fallBackDir )
        
{
            GWTrace.EnteringMethod( MethodBase.GetCurrentMethod() );
            
if (System.IO.Directory.Exists( newDir ) ) return newDir;

            
try 
            
{
                System.IO.Directory.CreateDirectory( newDir );
                
if (System.IO.Directory.Exists( newDir ) )
                    
return newDir;
                
else
                    
return fallBackDir;
            }

            
catch (Exception ex )
            
{
                GWTrace.Trace( TraceLevel.Error,
                    
"There was an error creating the directory {0} : {1}",
                    newDir,
                    ex.ToString() );
                
return fallBackDir;
            }

        }

    }

}



/*
 * Class       : DbErrorLog
 * Namespace   : GW.MonitorServices.Logging
 * Assembly    : GW.MonitorServices.Logging
 * Author       : Kevin Hoffman
 * Description : Class that is responsible for logging system errors to the database.
 
*/


using System;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using GW.CMPServices;
using GW.MonitorServices;

namespace GW.MonitorServices.Logging
{
    
/// <summary>
    
/// 这个类用来把错误状态保存到数据库里,一会儿我把保存错误的表结构发给大家哦
    
/// </summary>

    public class DbErrorLog
    
{
        
//防治实例化本垒
        private DbErrorLog() { }

        
/// <summary>
        
/// 记录异常
        
/// </summary>
        
/// <param name="e">要记录的异常</param>
        
/// <returns>返回的异常编号</returns>

        public static long LogError( Exception e )
        
{
            
return LogError( e, string.Empty );
        }


        
/// <summary>
        
/// 重载的方法,根据原始异常和错误信息把这些信息记录到数据库里,这里使用的不是普通的SqlHelper方式来保存数据库
        
/// 而是用一种托管容器持久性的方法来完成的,也就是CMP模式,如果你看不懂,你可以修改成你能看懂的方式,但是要引入相关的
        
/// System.Data.SqlClient等空间,不过以后我有空会给大家介绍在.NET里如何实现EJB里的CMP模式的.
        
/// </summary>
        
/// <param name="e">原始异常</param>
        
/// <param name="rootMessage">错误消息</param>
        
/// <returns>返回错误号</returns>

        public static long LogError( Exception e, string rootMessage )
        
{
            
//跟踪输出本方法
            GWTrace.EnteringMethod( MethodBase.GetCurrentMethod() );
            
            
//创建一个字符串缓存类,用来保存一些错误状态的相信信息,一会儿就要把他们保存到数据库里
            StringBuilder buffer = new StringBuilder(2048);
            
try
            
{
                
if (rootMessage.Length > 0)
                    buffer.AppendFormat( 
"{0}\n\n", rootMessage );        //原始错误信息
                buffer.Append( MonitorUtilities.ExpandStackTrace( e ) );//发生错误的堆栈

                DbErrorEntry dbEntry 
= new DbErrorEntry();                //创建一个错误记录实体,一会儿我就贴它的源码
                dbEntry.ErrorMessage = buffer.ToString();                //设置错误信息
                dbEntry.ExtendedInfo = MonitorUtilities.GetProcessInfo();//设置扩展信息
                dbEntry.ServerName = MonitorUtilities.GetMachineName();    //设置发生错误的机器

                SqlPersistenceContainer sqlPC 
= new SqlPersistenceContainer(
                    CMPServices.CMPConfigurationHandler.ContainerMaps[
"ERROR_LOG"] );    //创建一个托管的容器
                
                
//执行容器的Insert方法,并把刚才创建的促无实体做为参数,要保证这一步成功要做一些额外的设置,在web.config里
                
//设置元数据,最后再给大家介绍,容器只执行CRUD这些标准操作,如果你想让一个容器不支持某个操作,需要重写它的那些
                
//标准操作,并引发异常,下次有机会再详细阐述,关于ORM,CMP的详细内容太复杂,而且争议也很大,这里不多唠叨了.
                sqlPC.Insert( dbEntry );                                
                
return dbEntry.ErrorId;
            }

            
catch (Exception ex )
            
{
                
//这里一定要把异常抛出去,因为一般不直接调用这个类来持久化错误,它一般由ErrorLog类来调用
                
//所以这里有错误要向外层抛出,否则外面就接不到了,也就无法处理了,对吧.
                
// this must be thrown so the generic log front-end can
                
// tell that there was a db error log failure and write the
                
// error to a disk file instead.
                GWTrace.Trace( TraceLevel.Error, "LogError Failed [{0}]", ex.ToString() );
                
throw new Exception("Database log failure", ex );
            }

        }

    }

}


/*
 * Class       : ErrorLog
 * Namespace   : GW.MonitorServices.Logging
 * Assembly    : GW.MonitorServices.Logging
 * Author       : Kevin Hoffman
 * Description : Class for handling all forms of error logging.
 
*/


using System;
using System.Configuration;
using System.Reflection;
using System.Diagnostics;

namespace GW.MonitorServices.Logging
{
    
/// <summary>
    
/// 错误日志类,本来来把程序运行中出现的错误持久化,方便以后分析,先视图把它放在数据库里,
    
/// 并在一个文本文件里做个标记,如果在把错误持久到数据库的过程中遇到了错误,就把信息保存在一个
    
/// html文件里,这样只要有错误发生始终会持久在一个安全的地方,方便你做错误分析,嘿嘿.
    
/// </summary>

    public class ErrorLog
    
{
        
/// <summary>
        
/// 错误日志标志的文件目录
        
/// </summary>

        private static string errorUrlPrefix;

        
/// <summary>
        
/// 只传入异常的构造函数,返回一个长整型的错误号
        
/// </summary>
        
/// <param name="appError">异常</param>
        
/// <returns>返回错误号</returns>

        public static long LogError( Exception appError )
        
{
            
return LogError( appError, string.Empty );
        }


        
/// <summary>
        
/// 根据原始异常和错误信息来持久化错误状态
        
/// </summary>
        
/// <param name="appError">引发错误的异常</param>
        
/// <param name="rootMessage">自定义信息</param>
        
/// <returns></returns>

        public static long LogError( Exception appError, string rootMessage )
        
{
            
//跟踪输出这个方法
            GWTrace.EnteringMethod( MethodBase.GetCurrentMethod() );
            rootMessage 
= ( rootMessage == null ? string.Empty : rootMessage.Trim() );
            
            
            
//试图把错误信息持久到数据库里,并在一个文本文件里做个标志,提示有个错误发生,已经把它持久到数据库里了,你去看看
            
//如果不能就把它持久到数据库里就执行catch后面的语句,把它试图持久到一个文本文件里
            try 
            
{
                
long errorId = DbErrorLog.LogError( appError, rootMessage );
                FileErrorLog.LogError( GWException.GetGWExceptionMessage( GWExceptionMessages.SeeDatabase ), 
                    ErrorViewUrlPrefix, errorId );
                
return errorId;
            }

            
catch (Exception dbError )
            
{
                
//跟踪输出一条信息,就说是在往数据库里写错误的时候失败了.
                GWTrace.Trace( TraceLevel.Error, "DB Error Log Attempt Failed" );
                
try 
                
{
                    
//把错误记录到错误日志文件里去
                    FileErrorLog.LogError( appError, rootMessage );

                    
//在错误标志文件里记录一条,数据库持久错误的消息,因为如果执行到这里,肯定是在把错误保存到数据库的时候
                    
//失败了,对吧,这时候还要返回一个错误码,以便让调用程序知道这个方法到底执行了哪一步,这也是一个小技巧
                    FileErrorLog.LogError( dbError, GWException.GetGWExceptionMessage( GWExceptionMessages.DbLogFailed ) );
                    
return -1;
                }

                
catch
                
{
                    
//如果都无法持久错误,哪只好跟踪输出一条错误信息了,就说在持久错误状态的时候失败了,一般这时候就是有严重错误了
                    
//最好在这里给管理员发封电子邮件,那些东西你自己扩展吧,你发条儿短信都行.
                    GWTrace.Trace( TraceLevel.Error, "Backup File Error Log Attempt Failed");
                    
return -2;
                }

            }

        }


        
/// <summary>
        
/// 错误标志文件的目录,错误发生的时候无论是把相信信息记录到数据库里了,还是记录到错误日志文本文件里了
        
/// 都会在一个错误标志文件里记录一条,表示发生了一条错误,你想查看具体错误就去数据库和去保存错误日志的目录
        
/// 里去看吧,就这个意思,所以你每天检查错误标志文件就行了.默认位置是/GadgetsWarehouse/ErrorLog/这里,当然
        
/// 你可以在web.config里设置,我不会翻译ErrorViewUrlPrefix这是什么意思哦,先把它翻译成错误标志文件目录吧
        
/// </summary>

        private static string ErrorViewUrlPrefix
        
{
            
get 
            
{
                
if ( errorUrlPrefix != nullreturn errorUrlPrefix;

                
string prefix = ConfigurationSettings.AppSettings["ErrorViewUrlPrefix"];
                prefix 
= ( prefix == null ? string.Empty : prefix.Trim() );

                
if (prefix.Length == 0 )
                    prefix 
= "/GadgetsWarehouse/ErrorLog/";

                errorUrlPrefix 
= prefix;
                
return errorUrlPrefix;
            }

        }

    }

}

posted @ 2005-08-01 16:42  蛙蛙王子  Views(3332)  Comments(12Edit  收藏  举报