CoreWCF / CoreWCF

Main repository for the Core WCF project
MIT License
1.66k stars 292 forks source link

App Insights Telemetry #619

Open roend83 opened 2 years ago

roend83 commented 2 years ago

When CoreWCF requests come into application insights, I just see POST /MyService.svc. There isn't any telemetry that tells me the actual service method that was called. I would like to see CoreWCF either support this natively or by including another CoreWCF package.

If someone can point me in the right direction on how to do this, I'd be willing to spend some time working this out.

edberg commented 2 years ago

You can have a look at this repository that fixed this for the full framework:

https://github.com/microsoft/ApplicationInsights-SDK-Labs

Maybe it can be ported to support CoreWCF?

mconnew commented 2 years ago

I would be open to having this be built in. My only criteria with the implementation is that it's pay to play. That is, if you aren't using App Insights, there's no runtime cost of checking if it's enabled all the time. We have a new middleware style pipeline so it might be worth adding it to our dispatch pipeline as an optional layer so that it gets put into the call path if used, but if not needed, it's out of the call path altogether.

birojnayak commented 2 years ago

@mconnew as long it's following open telemetry standard we should be good...

mconnew commented 2 years ago

App Insights support is based off of open telemetry now. So that should be fine.

roend83 commented 2 years ago

I was able to get this working in my application by including the Microsoft.ApplicationInsights.AspNetCore package, registering app insights on startup, and adding this service behavior:

public class AppInsightsTelemetryServiceBehavior : IServiceBehavior
{
    private IHttpContextAccessor HttpContextAccessor { get; }

    public AppInsightsTelemetryServiceBehavior(IHttpContextAccessor httpContextAccessor)
    {
        HttpContextAccessor = httpContextAccessor;
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        var handler = new RequestHandler(HttpContextAccessor);

        foreach (var dispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>())
        {
            dispatcher.ErrorHandlers.Insert(0, handler);

            foreach (var endpoint in dispatcher.Endpoints)
            {
                endpoint.DispatchRuntime.MessageInspectors.Insert(0, handler);
            }
        }
    }

    private class RequestHandler : IDispatchMessageInspector, IErrorHandler
    {
        private IHttpContextAccessor HttpContextAccessor { get; }

        public RequestHandler(IHttpContextAccessor httpContextAccessor)
        {
            HttpContextAccessor = httpContextAccessor;
        }

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            SetRequestTelemetry();
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            SetRequestTelemetry();
        }

        public bool HandleError(Exception error)
        {
            return false;
        }

        private void SetRequestTelemetry()
        {
            var requestTelemetry = HttpContextAccessor.HttpContext.Features.Get<RequestTelemetry>();
            if (requestTelemetry is null) return;

            var context = OperationContext.Current;
            if (context is null) return;

            var dispatchRuntime = context.EndpointDispatcher.DispatchRuntime;
            var operation = context.EndpointDispatcher.DispatchRuntime.Operations
                .SingleOrDefault(operation => operation.Action == context.IncomingMessageHeaders.Action);
            if (operation is null) return;

            requestTelemetry.Name = $"{dispatchRuntime.Type.Name}/{operation.Name}";
        }
    }
}

Most of this logic is borrowed and slightly modified from the ApplicationInsights-SDK-Labs project. If this is useful to others I can package it up on my own or add it as a new project in CoreWCF. Let me know what you think.

roend83 commented 2 years ago

It ends up logging out telemetry that looks like this image