WCF Tracing in ASP.NET Core
Asked Answered
E

3

12

We used to use WCF over ASP.NET and recently switched to WCF over ASP.NET Core. This was quite hard to implement because ASP.Net Core doesn't support WCF out of the box. For one thing, the whole web.config XML configuration model has been dumped in ASP.NET Core so we cannot configure WCF tracing there.

I.e. this document is useless: https://learn.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing

We had to hack ASP.NET Core by putting a http proxy between WCF and port 80. WCF is actually running on another port.

The question is, how do we enable WCF tracing if ASP.NET Core doesn't pay attention to the web.config?

Entourage answered 30/8, 2018 at 6:19 Comment(3)
WCF can be used like a RestFul service by using the webhttp binding. So it can be used instead of a webApi projectRubiaceous
WCF is too complex, and that's why tracing is needed. Why do you need tracing in ASP.NET Core? Separate the two please, and don't trouble yourself with an approach that nobody else goes.Seriocomic
You think I want to support a dead technology? If there was an option to get rid of it, I would. Don't trouble yourself with unhelpful comments.Entourage
M
17

In case of client side tracing I used custom endpoint behaviour (IEndpointBehavior) with custom message logging inspector (IClientMessageInspector) to get input and output messages.

Client initialization:

_serviceClient = new MyCustomServiceClient();
_serviceClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(_configParams.ServiceUri);
_serviceClient.Endpoint.EndpointBehaviors.Add(new EndpointLoggingBehavior("MyCustomService"));

Implementation of EndpointLoggingBehavior:

public class EndpointLoggingBehavior : IEndpointBehavior
    {
        public EndpointLoggingBehavior(string serviceName)
        {
            _serviceName = serviceName;
        }

        private readonly string _serviceName;

        public void AddBindingParameters(ServiceEndpoint endpoint,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new MessageLoggingInspector(_serviceName));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }

Implementation of MessageLoggingInspector:

public class MessageLoggingInspector : IClientMessageInspector
    {
        private readonly string _serviceName;
        public MessageLoggingInspector(string serviceName)
        {
            _serviceName = serviceName;
        }
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            // copying message to buffer to avoid accidental corruption
            var buffer = reply.CreateBufferedCopy(int.MaxValue);
            reply = buffer.CreateMessage();
            // creating copy
            var copy = buffer.CreateMessage();
            //getting full input message
            var fullInputMessage = copy.ToString();

        }
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // copying message to buffer to avoid accidental corruption
            var buffer = request.CreateBufferedCopy(int.MaxValue);
            request = buffer.CreateMessage();
            // creating copy
            var copy = buffer.CreateMessage();
            //getting full output message
            var fullOutputMessage = copy.ToString();
            return null;
        }
    }

Then, of course, you will need to write these messages to any storage.

Mathre answered 27/2, 2019 at 15:43 Comment(2)
Petr, thank you for your tips, I referenced to this one from my Q&A. One suggestion: to add a comment like "Then, of course.." after "copy.ToString();" in the code, e.g. "// writing message to a storage or to a logger". WCF is so hard to learn - extremely too hard from my point of view, it's the oppposite to the golden hammer antipattern - so any tip in that code can save hours. In my case I injected a logger in MessageLoggingInspector's constructor.Amund
Tried the code, but the response body that contains a soap fault looks like this: <SOAP-ENV:Body>... stream ...</SOAP-ENV:Body>. I don't know why "... stream ..." happens.Depurate
S
2

You'll use ETW Tracing for WCF on .NET Core

https://github.com/dotnet/wcf/blob/master/Documentation/HowToUseETW.md

In my experience, you have some limitations

  1. Tracing is on for All WCF Apps, rather than configuring for a single app through config file
  2. You cannot output Messages with ETW tracing
  3. SvcTraceViewer.exe doesn't work well for trace review, you'll need to move to PerfView.exe which may present a learning curve

Benefits of ETW

  1. You avoid the performance hit from classic forms of tracing
  2. No more config change to start/stop tracing
Singleton answered 2/2, 2019 at 22:7 Comment(0)
S
0

Building on the excellent answer by Petr Pokrovskiy, this is how you can redirect the trace to the standard .NET Core log:

Client initialization:

ILogger<MyCustomService> = ...; // use dependency injection to get instance
_serviceClient = new MyCustomServiceClient();
_serviceClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(_configParams.ServiceUri);
_serviceClient.Endpoint.SetTraceLogging(logger);

Implementation of SetTraceLogging:

using Microsoft.Extensions.Logging;
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Common.ServiceModel.Logging
{
    public static class ServiceEndpointExtensions
    {
        public static void SetTraceLogging(this ServiceEndpoint serviceEndpoint, ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException(nameof(logger));
            if (logger.IsEnabled(LogLevel.Trace))
                serviceEndpoint.EndpointBehaviors.Add(new ClientMessageLoggingBehavior(logger));
        }
    }

    internal sealed class ClientMessageLoggingBehavior :
        IEndpointBehavior
    {
        private readonly ILogger _logger;

        public ClientMessageLoggingBehavior(ILogger logger)
        {
            _logger = logger;
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger(_logger));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }

    internal sealed class ClientMessageLogger :
        IClientMessageInspector
    {
        private readonly ILogger _logger;

        public ClientMessageLogger(ILogger logger)
        {
            this._logger = logger;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            // copying message to buffer to avoid accidental corruption
            reply = Clone(reply);
            this._logger.LogTrace("Received SOAP reply:\r\n{0}", reply.ToString());

        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // copying message to buffer to avoid accidental corruption
            request = Clone(request);
            this._logger.LogTrace("Sending SOAP request:\r\n{0}", request.ToString());
            return null;
        }

        private static Message Clone(Message message)
        {
            return message.CreateBufferedCopy(int.MaxValue).CreateMessage();
        }
    }
}
Skees answered 1/10, 2021 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.