65 votes

Comment créer un gestionnaire d'exception global pour un service WCF ?

Je veux enregistrer toutes les exceptions côté serveur .

En ASP.NET, j'écris quelque chose comme ceci dans Global.asax.cs, mais cela fonctionnera-t-il aussi pour un service WCF ?

public class Global : HttpApplication
{
    protected void Application_Error(object sender, EventArgs e)
    {
        Exception unhandledException = Server.GetLastError();

        //Log exception here
        ...
    }
} 

MISE À JOUR : Je ne veux pas avoir un try...catch pour chaque [OperationContract] dans mon fichier .svc. En bref... Je veux m'assurer que toutes les exceptions lancées par mon service sont enregistrées par log4net. Je ne parle pas de la façon dont le client gère les exceptions.

50voto

Marc Gravell Points 482669

Vous pouvez créer un journal d'erreur de WCF en mettant en application IErrorHandler et l'associe au service ; typiquement (pour la journalisation) vous retournerez false de HandleError (ce qui permet à d'autres gestionnaires de s'exécuter), et de consigner l'erreur soit dans le fichier HandleError (en utilisant le Exception ) ou dans ProvideFault (en utilisant le ref Message fault ).

J'applique ce gestionnaire en écrivant un comportement personnalisé (héritant de BehaviorBase ), qui (en ApplyDispatchBehavior ) ajoute le gestionnaire d'erreurs à endpointDispatcher.ChannelDispatcher.ErrorHandlers s'il n'y est pas déjà.

Le comportement peut être appliqué via la configuration.

0 votes

Bon lien ; j'aurais +1 ce lien si je pouvais

1 votes

@LukasCenovsky +1 pour le lien qui ne contient que l'essentiel.

0 votes

Marc, est-ce qu'il attrape TOUTES les exceptions ? J'obtiens ceci dans CurrentDomain.UnhandledException. Que dois-je faire à ce sujet ? Il fait planter mon application serveur. (contexte unique, concurrence multiple) System.ServiceModel.CommunicationObjectFaultedException : L'objet de communication, System.ServiceModel.Channels.SecurityChannelListener`1+SecurityReplySessionChannel[System.ServiceModel.Channels.IReplySessionChannel], ne peut pas être utilisé pour la communication car il est dans l'état Faulted. at System.ServiceModel.Channels.CommunicationObject.ThrowIfFaulted()

24voto

SkyBlade002 Points 21

J'ai été confronté à ce problème moi-même et après de nombreux onglets de recherche, y compris des articles liés à d'autres réponses, voici exactement ce dont on a besoin.

Voici la classe ErrorHandler dont vous aurez besoin...

Imports System.ServiceModel.Configuration
Imports System.ServiceModel.Description
Imports System.ServiceModel.Dispatcher

''' <summary>
''' This class defines a global ErrorHandler, that allows us to control the
''' fault message returned to the client and perform custom error processing
''' like logging.
''' </summary>
Public Class ErrorHandler
    Implements IErrorHandler

    ''' <summary>
    ''' This method will be called whenever an exception occurs. Therefore,
    ''' we log it and then return false so the error can continue to propagate up the chain.
    ''' </summary>
    ''' <param name="ex">Exception being raised.</param>
    ''' <returns>False to let the error propagate up the chain, or True to stop the error here.</returns>
    Public Function HandleError(ByVal ex As Exception) As Boolean Implements IErrorHandler.HandleError
        'Unknown error occurred at the Service layer, log the event
        logEvent("Encountered an unknown error at the Service layer.", ex, EventLogEntryType.Error)

        Return False
    End Function

    ''' <summary>
    ''' This method is optionally used to transform standard exceptions into custom
    ''' FaultException(Of TDetail) that can be passed back to the service client.
    ''' </summary>
    ''' <param name="ex">Exception being raised.</param>
    ''' <param name="version">SOAP version of the message.</param>
    ''' <param name="fault">Message object that is returned to the client.</param>
    Public Sub ProvideFault(ByVal ex As Exception, ByVal version As Channels.MessageVersion, ByRef fault As Channels.Message) Implements IErrorHandler.ProvideFault
    End Sub

End Class

''' <summary>
''' This class defines a ServiceBehavior, that will allow us to add our
''' custom ErrorHandler class, defined above, to each channel we have a
''' service running on.
''' </summary>
Public Class ErrorServiceBehavior
    Implements IServiceBehavior

    Public Sub AddBindingParameters(serviceDescription As ServiceDescription, serviceHostBase As ServiceHostBase, endpoints As ObjectModel.Collection(Of ServiceEndpoint), bindingParameters As Channels.BindingParameterCollection) Implements IServiceBehavior.AddBindingParameters
    End Sub

    Public Sub ApplyDispatchBehavior(serviceDescription As ServiceDescription, serviceHostBase As ServiceHostBase) Implements IServiceBehavior.ApplyDispatchBehavior
        'Enumerate all channels and add the error handler to the collection
        Dim handler As New ErrorHandler()
        For Each dispatcher As ChannelDispatcher In serviceHostBase.ChannelDispatchers
            dispatcher.ErrorHandlers.Add(handler)
        Next
    End Sub

    Public Sub Validate(serviceDescription As ServiceDescription, serviceHostBase As ServiceHostBase) Implements IServiceBehavior.Validate
    End Sub

End Class

''' <summary>
''' This class defines a BehaviorExtensionElement, so that we can
''' use the ErrorServiceBehavior class, defined above, in our App.config.
''' </summary>
Public Class ErrorHandlerBehavior
    Inherits BehaviorExtensionElement

    Protected Overrides Function CreateBehavior() As Object
        Return New ErrorServiceBehavior()
    End Function

    Public Overrides ReadOnly Property BehaviorType As Type
        Get
            Return GetType(ErrorServiceBehavior)
        End Get
    End Property

End Class

Ensuite, ces sections doivent être ajoutées/mises à jour dans le fichier App.config de votre projet de service...

<system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <!-- Add in our custom error handler -->
      <add name="ErrorLogging" type="Service.ErrorHandlerBehavior, Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behaviorExtensions>
  </extensions>
  <services>
    <service name="Service.Service" behaviorConfiguration="Service.ServiceBehavior">
      <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttp"
      contract="SentinelService.IService"/>
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Service.ServiceBehavior">
        <serviceMetadata httpGetEnabled="True" />
        <serviceDebug includeExceptionDetailInFaults="False" />
        <!-- Add in our custom error handler, from behaviorExtensions element -->
        <ErrorLogging />
      </behavior>
    </serviceBehaviors>
  </behaviors>

Plus précisément, une behaviorExtension doit être ajoutée, son nom doit être ajouté à la section de comportement, la section de comportement doit être nommée et cette section de comportement doit être la behaviorConfiguration du service.

Faites attention à l'attribut "type" de la behaviorExtension, il doit être exact. Si vous n'êtes pas sûr, vous pouvez utiliser la méthode suivante pour le déterminer GetType(ErrorHandlerBehavior).AssemblyQualifiedName .

Si vous êtes intéressé, voici le code derrière la fonction logEvent que j'utilise...

''' <summary>
''' Logs a message and optional exception to the application event log.
''' </summary>
''' <param name="message">String message to log.</param>
''' <param name="ex">Exception to log.</param>
''' <param name="eventType">EventLogEntryType indicating the message severity.</param>
Public Sub logEvent(ByVal message As String, Optional ByVal ex As Exception = Nothing, _
                    Optional ByVal eventType As EventLogEntryType = EventLogEntryType.Information)
    'Initialize report
    Dim report As String = message + vbNewLine + vbNewLine

    'Create eventLogger
    Dim eventLogger As New EventLog()

    'Register event source, add any Exception information to the report, and then log it
    Try
        'Register the app as an Event Source
        If Not EventLog.SourceExists("MyAppName") Then
            EventLog.CreateEventSource("MyAppName", "Application")
        End If

        If ex IsNot Nothing Then
            'Force eventType to error
            eventType = EventLogEntryType.Error

            'Add Exception Information to report
            report += "Exception Information:" + vbNewLine
            Dim currentException As Exception = ex
            Dim exCount As Integer = 1
            While (currentException IsNot Nothing)
                report += Space(5) + If(exCount = 1, "Message:", "Inner Exception:") + vbNewLine
                report += Space(10) + currentException.Message + vbNewLine
                report += Space(5) + "StackTrace:" + vbNewLine
                report += Space(10) + currentException.StackTrace + vbNewLine
                report += vbNewLine

                currentException = currentException.InnerException
                exCount += 1
            End While
        End If
    Catch reportException As Exception
        'This catch ensures that no matter what some error report is logged.
        report += vbNewLine + vbNewLine + "PARTIAL REPORT!!!...AN ERROR WAS ENCOUNTERED GENERATING ERROR REPORT."
    Finally
        Try
            'Log report
            eventLogger.Source = "MyAppName"
            eventLogger.WriteEntry(report, eventType)
        Catch logEventException As Exception
            'Eat it...nothing can be done at this point and we do
            'not want the application to crash over an error report
            'if we can prevent it
        End Try
    End Try
End Sub

4 votes

J'obtiens un avertissement dans mon web.config à propos de "l'élément 'behavior' a un élément enfant invalide 'MyBehaviorName'. Liste des éléments possibles..." Existe-t-il un moyen de faire disparaître cet avertissement ? Merci pour l'exemple.

2 votes

Il s'agit de la réponse la plus complète. Elle contient tout ce qui est nécessaire, de manière clairement expliquée.

0 votes

Up 1 pour avoir fourni une réponse en VB.Net. =) C'est également agréable et concis.

24voto

Marcel Points 3616

Ce n'est pas une réponse en soi, mais un addendum au excellente réponse fourni par SkyBlade002 en fournissant le code donné en C#, pour la commodité de tout codeur C# :

Voici mon code (dans ErrorHandler.cs) :

/// <summary>
///     This class defines a global ErrorHandler, that allows us to control the fault message returned to the client and
///     perform custom error processing like logging.
/// </summary>
public class ErrorHandler : IErrorHandler {
    private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    ///     Enables the creation of a custom <see cref="T:System.ServiceModel.FaultException`1" /> that is returned from an
    ///     exception in the course of a service method.
    /// </summary>
    /// <remarks>
    ///     This method is optionally used to transform standard exceptions into custom FaultException(Of TDetail) that
    ///     can be passed back to the service client.
    /// </remarks>
    /// <param name="error">The <see cref="T:System.Exception" /> object thrown in the course of the service operation.</param>
    /// <param name="version">The SOAP version of the message.</param>
    /// <param name="fault">
    ///     The <see cref="T:System.ServiceModel.Channels.Message" /> object that is returned to the client, or
    ///     service, in the duplex case.
    /// </param>
    public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {}

    /// <summary>
    ///     This method will be called whenever an exception occurs. Therefore,
    ///     we log it and then return false so the error can continue to propagate up the chain.
    /// </summary>
    /// <param name="error">Exception being raised.</param>
    /// <returns>False to let the error propagate up the chain, or True to stop the error here.</returns>
    public bool HandleError(Exception ex) {
        Log.Error(ex); //TODO your own logging
        return false;
    }

Voici mon code (dans ErrorHandlerBehavior.cs) :

/// <summary>
///     This class defines a BehaviorExtensionElement, so that we can use the ErrorServiceBehavior class, defined above, in
///     our App.config.
/// </summary>
public class ErrorHandlerBehavior : BehaviorExtensionElement {
    /// <summary>
    ///     Gets the type of behavior.
    /// </summary>
    public override Type BehaviorType {
        get { return typeof (ErrorServiceBehavior); }
    }

    /// <summary>
    ///     Creates a behavior extension based on the current configuration settings.
    /// </summary>
    /// <returns>
    ///     The behavior extension.
    /// </returns>
    protected override object CreateBehavior() {
        return new ErrorServiceBehavior();
    }
}

Voici mon code (dans ErrorServiceBehavior.cs) :

/// <summary>
///     This class defines a ServiceBehavior, that will allow us to add our custom ErrorHandler class, defined above, to
///     each channel we have a service running on.
/// </summary>
public class ErrorServiceBehavior : IServiceBehavior {
    /// <summary>
    ///     Provides the ability to inspect the service host and the service description to confirm that the service can run
    ///     successfully.
    /// </summary>
    /// <param name="serviceDescription">The service description.</param>
    /// <param name="serviceHostBase">The service host that is currently being constructed.</param>
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {}

    /// <summary>
    ///     Provides the ability to pass custom data to binding elements to support the contract implementation.
    /// </summary>
    /// <param name="serviceDescription">The service description of the service.</param>
    /// <param name="serviceHostBase">The host of the service.</param>
    /// <param name="endpoints">The service endpoints.</param>
    /// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {}

    /// <summary>
    ///     Provides the ability to change run-time property values or insert custom extension objects such as error handlers,
    ///     message or parameter interceptors, security extensions, and other custom extension objects.
    /// </summary>
    /// <param name="serviceDescription">The service description.</param>
    /// <param name="serviceHostBase">The host that is currently being built.</param>
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {
        //Enumerate all channels and add the error handler to the collection
        var handler = new ErrorHandler();
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) {
            dispatcher.ErrorHandlers.Add(handler);
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X