在控制台应用程序中使用ELMAH

65

我刚开始使用ELMAH,感到非常满意。我的团队支持大量的Web应用程序,我特别兴奋的是ELMAH可以让我们将每个应用程序的异常保存到同一个MS SQL数据库表。

我们还支持一些控制台、DLL和桌面应用程序。是否可以使用ELMAH DLL将这些应用程序中的异常记录到同一位置?


我计划在Windows服务中实现这个功能。只是想确认一下,如果我们这样做,是否可能会有任何性能问题/其他问题?对我来说,这似乎主要是针对基于Web的应用程序。我没有找到太多关于如何在Windows应用程序中实现它的材料。谢谢。 - hangar18
7个回答

78
我们需要在控制台应用程序和Windows服务中记录日志,除了我们的ASP.NET站点。我使用了答案(ErrorLog.GetDefault(null);),它很好地工作,直到我需要发送电子邮件。

所以,这是我的解决方案。它处理了日志、电子邮件、推文和过滤(在配置文件和代码中都可以),我还将主调用包装为Exception的扩展,因此可以像这样调用:catch(Exception ex) { ex.LogToElmah(); }

要在代码中进行过滤,请挂钩相应的.Filtering事件:ElmahExtension.ErrorLog.Filtering += new ExceptionFilterEventHandler(ErrorLog_Filtering);

代码:

using System;
using System.Web;
using Elmah;
namespace System
{
    public static class ElmahExtension
    {
        public static void LogToElmah(this Exception ex)
        {
            if (HttpContext.Current != null)
            {
                ErrorSignal.FromCurrentContext().Raise(ex);
            }
            else
            {
                if (httpApplication == null) InitNoContext();
                ErrorSignal.Get(httpApplication).Raise(ex);
            }
        }

            private static HttpApplication httpApplication = null;
            private static ErrorFilterConsole errorFilter = new ErrorFilterConsole();

            public static ErrorMailModule ErrorEmail = new ErrorMailModule();
            public static ErrorLogModule ErrorLog = new ErrorLogModule();
            public static ErrorTweetModule ErrorTweet = new ErrorTweetModule();

            private static void InitNoContext()
            {
                httpApplication = new HttpApplication();
                errorFilter.Init(httpApplication);

                (ErrorEmail as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorEmail);

                (ErrorLog as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorLog);                

                (ErrorTweet as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorTweet);
            }

            private class ErrorFilterConsole : ErrorFilterModule
            {
                public void HookFiltering(IExceptionFiltering module)
                {
                    module.Filtering += new ExceptionFilterEventHandler(base.OnErrorModuleFiltering);
                }
            }
    }
}

此外,为了使其工作,您需要在项目中添加对System.Web.dll的引用。

编辑:根据评论,此代码仅在您的配置文件中具有<errorMail async="false"/>时发送电子邮件。如果您想保留配置文件中的<errorMail async="true"/>(当HttpContext.Current可用时使用),请参考此代码片段


6
在单元测试中,直到我设置了<elmah><errorMail async="false" /></elmah>,这个方法才起作用。之前我将async设置为true。 - Todd Smith
2
我复制了你的代码,在我的异常处理中加入了 ex.LogToElmah();,虽然代码可以正常运行,但是当我查看 elmah.axd 时,没有任何日志被记录。在本地主机上,我没有使用电子邮件发送或其他任何东西,所以我不确定是否是这个原因。虽然没有崩溃,但我需要连接其他东西吗? - chobo2
3
使用此扩展程序。将configsection和elmah配置添加到app.config中,并设置<errorMail async="false"/>,这样我就能使其正常工作了。 - Jim Wolff
1
错误也没有出现在我的日志中(我使用的是SQL服务器日志)。我不得不在我的App.config中设置applicationName属性才能使其工作。即 <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="elmah-sqlserver" applicationName="/LM/W3SVC/3/ROOT" /> - asgeo1
我有点困惑,我只需添加一个带有上述代码的单独类,将errorlog设置添加到App.config中。还需要做其他事情吗? 此外,我无法在主项目的Web.Config中找到applicationName,以便将其包含在控制台应用程序的App.config中。 - Shyamal Parikh
显示剩余3条评论

69

我们这里的情况与你们完全相同。在所有Web应用程序中运行ELMAH。其中一些具有基于控制台的调度程序。

经过对源代码的深入挖掘,以下代码似乎可以工作:

            ErrorLog errorLog = ErrorLog.GetDefault(null);
            errorLog.ApplicationName = "/LM/W3SVC/1/ROOT/AppName";
            errorLog.Log(new Error(ex));
上述代码唯一的实际问题在于你需要在配置文件中保留应用程序名称,以便能够在ELMAH.axd查看器中查看条目。因此,在我们的通用错误处理代码中,我们执行以下操作:
        if (HttpContext.Current != null)
            ErrorSignal.FromCurrentContext().Raise(ex);
        else
        {
            ErrorLog errorLog = ErrorLog.GetDefault(null);
            errorLog.ApplicationName = ErrorHandling.Application;
            errorLog.Log(new Error(ex));
        }

2
嗯,非常酷,但似乎我无法让它在控制台应用程序中发生错误时发送电子邮件。有什么想法吗? - teedyay
3
似乎errorLog.Log绕过了所有其他的监听器。有人知道更好的方法吗? - Neil Bostrom
@Duke,我喜欢这种方法,但不幸的是,我的模块位于一个单独的项目中。我发现如果你像这样引用elmah来在非Web应用程序中使用它,它将无法编译,直到你添加System.Web Dll。 - Joy

14
如果你只想通过电子邮件发送日志而不是使用http,可以按照以下方式操作:
    public class MyElmahMail: ErrorMailModule
    {
        public MyElmahMail()
        {
//this basically just gets config from errorMail  (app.config)
            base.OnInit(new HttpApplication());
        }
        public void Log(Error error)
        {
//just send the email pls
            base.ReportError(error);
        }
    }

//to call it
var mail = new MyElmahMail();
mail.Log(new Error(new NullReferenceException()));//whatever exception u want to log

就 app.config 而言

//Under configSections
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
    </sectionGroup>

并且

  <elmah>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="C:\Elmah.Error" applicationName="MyAwesomeApp" />
    <errorMail from="from@email.com" to="to@email.com" />
  </elmah>

还有您选择的SMTP设置。

全部完成。:-)


3
这个答案无疑是最佳的!在尝试了所有方法后,第一次尝试这个方法就成功了! - Egli Becerra

5

编辑:这是可以做到的 - 请参见this答案。


我确信你做不到这件事。我会尽力找到相关材料。

http://groups.google.com/group/elmah/browse_thread/thread/f214c4f782dc2bf4/d96fe43b60765f0c?lnk=gst&q=app#d96fe43b60765f0c

根据我在谷歌组中搜索到的信息,似乎不可能实现...由于ELMAH是基于HttpHandlers(一种asp.net结构),所以只能在ASP.NET上使用。

话虽如此,你仍然可以在控制台应用程序中利用它。ELMAH提供了一种引发错误的方法,因此你可以将ELMAH包装在异常处理中,然后通过以下方式发出错误信号:

ErrorSignal.FromCurrentContext().Raise(new NotSupportedException());

这意味着将整个应用程序包装在异常处理程序和信号中。这可能需要一些调整才能实现,但我认为完全有可能。
如果需要,这是ELMAH代码存储库的链接。

谢谢。我明白它不是围绕这个构建的,但似乎其他人可能需要共享一个公共错误数据库。 - Michael La Voie

2

好的,既然我不能评论,那我就在这里发帖,也许有人会看到。

在遵循Brian的方法和评论者的建议后,我成功地使电子邮件工作了,但我仍然没有看到SQL消息被记录,尽管我已经设置了applicationName。我没有意识到它们实际上已经被记录了,只是因为applicationName必须与您的web.config相同才能查看它。

我的web.config没有指定applicationName,所以它默认为“/LM/W3SVC/2/ROOT”,这基本上就是“asgeo1”评论的内容,尽管我没有意识到它必须是相同的。

由于我真的没有任何错误需要关注,所以我在我的web.config和app.config中配置了相同的applicationName,现在一切都像冠军一样显示出来了。

<errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ELMAH" applicationName="MyAppName" />

2

对于那些需要将Brian Chance的答案移植到VB.NET的人:

Imports System
Imports System.Web
Imports Elmah
Namespace System
    Public NotInheritable Class ElmahExtension
        Private Sub New()
        End Sub
        <System.Runtime.CompilerServices.Extension> _
        Public Shared Sub LogToElmah(ex As Exception)
            If HttpContext.Current IsNot Nothing Then
                ErrorSignal.FromCurrentContext().Raise(ex)
            Else
                If httpApplication Is Nothing Then
                    InitNoContext()
                End If
                ErrorSignal.[Get](httpApplication).Raise(ex)
            End If
        End Sub

        Private Shared httpApplication As HttpApplication = Nothing
        Private Shared errorFilter As New ErrorFilterConsole()

        Public Shared ErrorEmail As New ErrorMailModule()
        Public Shared ErrorLog As New ErrorLogModule()
        Public Shared ErrorTweet As New ErrorTweetModule()

        Private Shared Sub InitNoContext()
            httpApplication = New HttpApplication()
            errorFilter.Init(httpApplication)

            TryCast(ErrorEmail, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorEmail)

            TryCast(ErrorLog, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorLog)

            TryCast(ErrorTweet, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorTweet)
        End Sub



    Private Class ErrorFilterConsole
        Inherits Elmah.ErrorFilterModule


        Public Sub HookFiltering([module] As Elmah.IExceptionFiltering)
            AddHandler [module].Filtering, New Elmah.ExceptionFilterEventHandler(AddressOf MyBase.OnErrorModuleFiltering)
        End Sub

    End Class


    End Class
End Namespace

然而,若只需要将错误日志记录到数据库中,以下内容就足够了:
If System.Web.HttpContext.Current Is Nothing Then
    Dim req As System.Web.HttpRequest = New System.Web.HttpRequest(String.Empty, "https://www.domain.tld", Nothing)
    Dim res As System.Web.HttpResponse = New System.Web.HttpResponse(Nothing)
    System.Web.HttpContext.Current = New System.Web.HttpContext(req, res)

    'Dim request As System.Web.Hosting.SimpleWorkerRequest = New System.Web.Hosting.SimpleWorkerRequest("/blah", "c:\inetpub\wwwroot\blah", "blah.html", Nothing, New System.IO.StringWriter())
    'System.Web.HttpContext.Current = New System.Web.HttpContext(request)

    System.Web.HttpContext.Current.ApplicationInstance = New System.Web.HttpApplication()

    Dim ErrorLog As New Elmah.ErrorLogModule()
    TryCast(ErrorLog, System.Web.IHttpModule).Init(System.Web.HttpContext.Current.ApplicationInstance)
End If

作为一个完整的解决方案:
Public parent As Elmah.ServiceProviderQueryHandler = Nothing




' https://dev59.com/cFfUa4cB1Zd3GeqPEyHF
Public Function Elmah_MS_SQL_Callback(objContext As Object) As System.IServiceProvider
    Dim container As New System.ComponentModel.Design.ServiceContainer(parent(objContext))
    Dim strConnectionString As String = COR.SQL.MS_SQL.GetConnectionString()

    Dim log As Elmah.SqlErrorLog = New Elmah.SqlErrorLog(strConnectionString)
    'Dim strApplicationName = System.Web.Compilation.BuildManager.GetGlobalAsaxType().BaseType.Assembly().FullName
    Dim strApplicationName As String = System.Reflection.Assembly.GetExecutingAssembly().FullName
    If Not String.IsNullOrEmpty(strApplicationName) Then
        log.ApplicationName = strApplicationName.Substring(0, strApplicationName.IndexOf(","))
    End If

    container.AddService(GetType(Elmah.ErrorLog), log)
    Return container
End Function ' Elmah_MS_SQL_Callback




Public Function Elmah_PG_SQL_Callback(objContext As Object) As System.IServiceProvider
    Dim container As New System.ComponentModel.Design.ServiceContainer(parent(objContext))
    Dim strConnectionString As String = COR.SQL.MS_SQL.GetConnectionString()

    Dim log As Elmah.PgsqlErrorLog = New Elmah.PgsqlErrorLog(strConnectionString)
    'Dim strApplicationName = System.Web.Compilation.BuildManager.GetGlobalAsaxType().BaseType.Assembly().FullName
    Dim strApplicationName As String = System.Reflection.Assembly.GetExecutingAssembly().FullName
    If Not String.IsNullOrEmpty(strApplicationName) Then
        log.ApplicationName = strApplicationName.Substring(0, strApplicationName.IndexOf(","))
    End If

    container.AddService(GetType(Elmah.ErrorLog), log)
    Return container
End Function ' Elmah_PG_SQL_Callback


' http://weblogs.asp.net/stevewellens/archive/2009/02/01/debugging-a-deployed-site.aspx
Public Sub Initialize()

    If System.Web.HttpContext.Current Is Nothing Then
        Dim req As System.Web.HttpRequest = New System.Web.HttpRequest(String.Empty, "https://www.domain.tld", Nothing)
        Dim res As System.Web.HttpResponse = New System.Web.HttpResponse(Nothing)
        System.Web.HttpContext.Current = New System.Web.HttpContext(req, res)

        'Dim request As System.Web.Hosting.SimpleWorkerRequest = New System.Web.Hosting.SimpleWorkerRequest("/blah", "c:\inetpub\wwwroot\blah", "blah.html", Nothing, New System.IO.StringWriter())
        'System.Web.HttpContext.Current = New System.Web.HttpContext(request)

        System.Web.HttpContext.Current.ApplicationInstance = New System.Web.HttpApplication()

        Dim ErrorLog As New Elmah.ErrorLogModule()
        TryCast(ErrorLog, System.Web.IHttpModule).Init(System.Web.HttpContext.Current.ApplicationInstance)
    End If



    parent = Elmah.ServiceCenter.Current

    If SQL.IsMsSql Then
        Elmah.ServiceCenter.Current = AddressOf Elmah_MS_SQL_Callback
    End If

    If SQL.IsPostGreSql Then
        Elmah.ServiceCenter.Current = AddressOf Elmah_PG_SQL_Callback
    End If
End Sub ' InitializeElmah

并且
Elmah.ErrorSignal.FromCurrentContext().Raise(New NotImplementedException("Test"))

只有在调用Initialize()之后才能起作用。


-5

ELMAH代表错误日志模块和处理程序 - 当然是指IHttpModuleIHttpHandler

控制台应用程序不使用HTTP,因此通常无法从为HTTP构建的模块和处理程序中受益太多。


有些人很难接受真相,因此你会收到负评。 我个人认为ELMAH是一个方形钉子,无法适应控制台应用程序的圆形孔。 自己编写一个小型汇编程序并不难,它可以完成两件事: 1.发送异常的电子邮件。 2.将异常记录到数据库中(甚至可以记录到ELMAH表中)。 然后在控制台应用程序中引用这个小型自定义程序集。 - MikeTeeVee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接