View POST request body in Application Insights
Asked Answered
L

10

108

Is it possible to view POST request body in Application Insights?

I can see request details, but not the payload being posted in application insights. Do I have to track this with some coding?

I am building a MVC core 1.1 Web Api.

POST request

Leavelle answered 9/3, 2017 at 3:54 Comment(1)
Keep in mind. The data in the request may hold private and sensitive information that is not for you to read and not to be stored in any log. Only use this for dev and test purposes.Pia
G
67

You can simply implement your own Telemetry Initializer:

For example, below an implementation that extracts the payload and adds it as a custom dimension of the request telemetry:

public class RequestBodyInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        var requestTelemetry = telemetry as RequestTelemetry;
        if (requestTelemetry != null && (requestTelemetry.HttpMethod == HttpMethod.Post.ToString() || requestTelemetry.HttpMethod == HttpMethod.Put.ToString()))
        {
            using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
            {
                string requestBody = reader.ReadToEnd();
                requestTelemetry.Properties.Add("body", requestBody);
            }
        }
    }
}

Then add it to the configuration either by configuration file or via code:

TelemetryConfiguration.Active.TelemetryInitializers.Add(new RequestBodyInitializer());

Then query it in Analytics:

requests | limit 1 | project customDimensions.body
Gloat answered 13/3, 2017 at 20:13 Comment(10)
It appears that the requestTelemetry.HttpMethod is being deprecated. I'm not sure where to go to look up the HttpMethod.Josphinejoss
@Josphinejoss according to the updated docs, for newer versions of AI .NET SDK it is now needed to be extracted from the request name property. example of request name would be: 'GET /values/{id}' More details here: learn.microsoft.com/en-us/azure/application-insights/…Gloat
@Gloat Can you please update your answer for the newer version?Complot
@ShyamalParikh you can use the HttpContext.Current.Request.HttpMethod instead of the requestTelemetry.HttpMethodHoopes
Keep in mind that this code gets executed twice: once before the request and once after the request. The request body is empty the second time, so you might want to check the length of the input stream before adding (or overriding) the property!Wilda
I had to reset the position of the request inputStream in order to read the body. HttpContext.Current.Request.InputStream.Position = 0;Inesita
Can I write the "Response Body" as well? If yes can someone share some guidelines or code snippet if possible.Vicereine
This code is not working as expected because the request body is already disposed.Flotsam
I'm getting an object disposed exception as well. Also, TelemetryConfiguration.Active is now deprecated. github.com/microsoft/ApplicationInsights-dotnet/issues/1152Iridescence
Vote for this feature at feedback.azure.com/forums/…Scorify
C
49

The solution provided by @yonisha is in my opinion the cleanest one available. However you still need to get your HttpContext in there and for that you need some more code. I have also inserted some comments which are based or taken from code examples above. It is important to reset the position of your request else you will lose its data.

This is my solution that I have tested and gives me the jsonbody:

public class RequestBodyInitializer : ITelemetryInitializer
{
    readonly IHttpContextAccessor httpContextAccessor;

    public RequestBodyInitializer(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        if (telemetry is RequestTelemetry requestTelemetry)
        {
            if ((httpContextAccessor.HttpContext.Request.Method == HttpMethods.Post ||
                 httpContextAccessor.HttpContext.Request.Method == HttpMethods.Put) &&
                httpContextAccessor.HttpContext.Request.Body.CanRead)
            {
                const string jsonBody = "JsonBody";

                if (requestTelemetry.Properties.ContainsKey(jsonBody))
                {
                    return;
                }

                //Allows re-usage of the stream
                httpContextAccessor.HttpContext.Request.EnableRewind();

                var stream = new StreamReader(httpContextAccessor.HttpContext.Request.Body);
                var body = stream.ReadToEnd();

                //Reset the stream so data is not lost
                httpContextAccessor.HttpContext.Request.Body.Position = 0;
                requestTelemetry.Properties.Add(jsonBody, body);
            }
        }
    }

Then also be sure to add this to your Startup -> ConfigureServices

services.AddSingleton<ITelemetryInitializer, RequestBodyInitializer>();

EDIT:

If you also want to get the response body I found it useful to create a piece of middleware (.NET Core, not sure about Framework). At first I took above approach where you log a response and a request but most of the time you want these together:

    public async Task Invoke(HttpContext context)
    {
        var reqBody = await this.GetRequestBodyForTelemetry(context.Request);

        var respBody = await this.GetResponseBodyForTelemetry(context);
        this.SendDataToTelemetryLog(reqBody, respBody, context);
    }

This awaits both a request and a response. GetRequestBodyForTelemetry is almost identical to the code from the telemetry initializer, except using Task. For the response body I have used the code below, I also excluded a 204 since that leads to a nullref:

public async Task<string> GetResponseBodyForTelemetry(HttpContext context)
{
    var originalBody = context.Response.Body;

    try
    {
        using (var memStream = new MemoryStream())
        {
            context.Response.Body = memStream;

            //await the responsebody
            await next(context);
            if (context.Response.StatusCode == 204)
            {
                return null;
            }

            memStream.Position = 0;
            var responseBody = new StreamReader(memStream).ReadToEnd();

            //make sure to reset the position so the actual body is still available for the client
            memStream.Position = 0;
            await memStream.CopyToAsync(originalBody);

            return responseBody;
        }
    }
    finally
    {
        context.Response.Body = originalBody;
    }
}
Coltun answered 25/4, 2018 at 13:47 Comment(11)
Thanks, your code worked. Can I write the "Response Body" as well? If yes can you please share some guidelines or code snippet if possible.Vicereine
I want to log "Response Body", any help?Vicereine
I also developed a middleware but wondering if it can be done with "Telemetry Initializer". Anyhow, great help.Vicereine
The thing is that when you use the initializer you don't have a response yet so you'll have to await that somehow. What I did not like was the fact that you get two seperate log items instead of them being in 1 telemetry item with request and response.Coltun
Yes, that's the problem:). However, I did modify my code to log item only once. @ColtunVicereine
I got the following error An error occurred while starting the application. I have configured it by : services.AddApplicationInsightsTelemetry(); services.AddApplicationInsightsTelemetryProcessor(typeof(RequestBodyInitializer));Snooperscope
FYI, EnableRewind() has moved to httpContextAccessor.HttpContext.Request.EnableBuffering() as of .NET Core 3.0Draughts
I just used this code in a ASP .NET 5 project and I have 2 problems: 1) ReadToAll() is not allowed, needs to be async. 2) I always get error: ObjectDisposedException: Cannot access a disposed object. Object name: 'HttpRequestStream'.Haworth
Vote for this feature at feedback.azure.com/forums/…Scorify
@JoanComasFdz: Initializer doesn't work, cause when it runs, the body stream is already disposed, cause the request is already being fullfilled. You have to write a middleware. The magic method to get within the middleware the telemetry object is httpContext.Features.Get<RequestTelemetry>(). With this knowledge and the given answer you should get it to work.Scene
@JoanComasFdz, have you got your issue resolved? I've encounter same error with you ObjectDisposedException: Cannot access a disposed object. Object name: 'HttpRequestStream'Lavalava
K
21

Update: I have put the logic below into a ready-to-use NuGet package. You can find more about the package here and about the topic itself here.


I choose the custom middleware path as it made things easier with HttpContext already being there.

public class RequestBodyLoggingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var method = context.Request.Method;

        // Ensure the request body can be read multiple times
        context.Request.EnableBuffering();

        // Only if we are dealing with POST or PUT, GET and others shouldn't have a body
        if (context.Request.Body.CanRead && (method == HttpMethods.Post || method == HttpMethods.Put))
        {
            // Leave stream open so next middleware can read it
            using var reader = new StreamReader(
                context.Request.Body,
                Encoding.UTF8,
                detectEncodingFromByteOrderMarks: false,
                bufferSize: 512, leaveOpen: true);

            var requestBody = await reader.ReadToEndAsync();

            // Reset stream position, so next middleware can read it
            context.Request.Body.Position = 0;

            // Write request body to App Insights
            var requestTelemetry = context.Features.Get<RequestTelemetry>();                              
            requestTelemetry?.Properties.Add("RequestBody", requestBody);
        }

        // Call next middleware in the pipeline
        await next(context);
    }
}

And this is how I log the response body

public class ResponseBodyLoggingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var originalBodyStream = context.Response.Body;

        try
        {
            // Swap out stream with one that is buffered and suports seeking
            using var memoryStream = new MemoryStream();
            context.Response.Body = memoryStream;

            // hand over to the next middleware and wait for the call to return
            await next(context);

            // Read response body from memory stream
            memoryStream.Position = 0;
            var reader = new StreamReader(memoryStream);
            var responseBody = await reader.ReadToEndAsync();

            // Copy body back to so its available to the user agent
            memoryStream.Position = 0;
            await memoryStream.CopyToAsync(originalBodyStream);

            // Write response body to App Insights
            var requestTelemetry = context.Features.Get<RequestTelemetry>();
            requestTelemetry?.Properties.Add("ResponseBody", responseBody);
        }
        finally
        {
            context.Response.Body = originalBodyStream;
        }
    }
}

Than add an extension method...

public static class ApplicationInsightExtensions
{
    public static IApplicationBuilder UseRequestBodyLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestBodyLoggingMiddleware>();
    }

    public static IApplicationBuilder UseResponseBodyLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ResponseBodyLoggingMiddleware>();
    }            
}

...that allows for a clean integration inside Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        
        // Enable our custom middleware
        app.UseRequestBodyLogging();
        app.UseResponseBodyLogging();
    }
    
    // ...
}

Don't forget to register the custom middleware components inside ConfigureServices()

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
            
    services.AddTransient<RequestBodyLoggingMiddleware>();
    services.AddTransient<ResponseBodyLoggingMiddleware>();
}
Kolkhoz answered 29/12, 2020 at 21:28 Comment(3)
This just works with .NET 5. Thanks a lot!Haworth
Great article. Thanks to share your work !Scorify
worked for me on .NET 5, thank you!Lavalava
C
19

Few days back, I got a similar requirement to log the request Body in Application insights with filtering out sensitive input user data from the payload. So sharing my solution. The below solution is developed for ASP.NET Core 2.0 Web API.

ActionFilterAttribute

I've used ActionFilterAttribute from (Microsoft.AspNetCore.Mvc.Filters namespace) which provides the Model via ActionArgument so that by reflection, those properties can be extracted which are marked as sensitive.

public class LogActionFilterAttribute : ActionFilterAttribute
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public LogActionFilterAttribute(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (context.HttpContext.Request.Method == HttpMethods.Post || context.HttpContext.Request.Method == HttpMethods.Put)
        {
            // Check parameter those are marked for not to log.
            var methodInfo = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor).MethodInfo;
            var noLogParameters = methodInfo.GetParameters().Where(p => p.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute))).Select(p => p.Name);

            StringBuilder logBuilder = new StringBuilder();

            foreach (var argument in context.ActionArguments.Where(a => !noLogParameters.Contains(a.Key)))
            {
                var serializedModel = JsonConvert.SerializeObject(argument.Value, new JsonSerializerSettings() { ContractResolver = new NoPIILogContractResolver() });
                logBuilder.AppendLine($"key: {argument.Key}; value : {serializedModel}");
            }

            var telemetry = this.httpContextAccessor.HttpContext.Items["Telemetry"] as Microsoft.ApplicationInsights.DataContracts.RequestTelemetry;
            if (telemetry != null)
            {
                telemetry.Context.GlobalProperties.Add("jsonBody", logBuilder.ToString());
            }

        }

        await next();
    }
}

The 'LogActionFilterAttribute' is injected in MVC pipeline as Filter.

 services.AddMvc(options =>
 {
       options.Filters.Add<LogActionFilterAttribute>();
 });

NoLogAttribute

In above code, NoLogAttribute attribute is used which should be applied on Model/Model's Properties or method parameter to indicate that value should not be logged.

public class NoLogAttribute : Attribute
{
}

NoPIILogContractResolver

Also, NoPIILogContractResolver is used in JsonSerializerSettings during serialization process

internal class NoPIILogContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = new List<JsonProperty>();

        if (!type.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute)))
        {
            IList<JsonProperty> retval = base.CreateProperties(type, memberSerialization);
            var excludedProperties = type.GetProperties().Where(p => p.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute))).Select(s => s.Name);
            foreach (var property in retval)
            {
                if (excludedProperties.Contains(property.PropertyName))
                {
                    property.PropertyType = typeof(string);
                    property.ValueProvider = new PIIValueProvider("PII Data");
                }

                properties.Add(property);
            }
        }

        return properties;
    }
}

internal class PIIValueProvider : IValueProvider
{
    private object defaultValue;

    public PIIValueProvider(string defaultValue)
    {
        this.defaultValue = defaultValue;
    }

    public object GetValue(object target)
    {
        return this.defaultValue;
    }

    public void SetValue(object target, object value)
    {

    }
}

PIITelemetryInitializer

To inject the RequestTelemetry object, I've to use ITelemetryInitializer so that RequestTelemetry can be retrieved in LogActionFilterAttribute class.

public class PIITelemetryInitializer : ITelemetryInitializer
{
    IHttpContextAccessor httpContextAccessor;

    public PIITelemetryInitializer(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        if (this.httpContextAccessor.HttpContext != null)
        {
            if (telemetry is Microsoft.ApplicationInsights.DataContracts.RequestTelemetry)
            {
                this.httpContextAccessor.HttpContext.Items.TryAdd("Telemetry", telemetry);
            }
        }
    }
}

The PIITelemetryInitializer is registered as

services.AddSingleton<ITelemetryInitializer, PIITelemetryInitializer>();

Testing feature

Following code demonstrates the usage of above code

Created a controller

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ILogger _logger;

    public ValuesController(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<ValuesController>();
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody, NoLog]string value)
    {

    }

    [HttpPost]
    [Route("user")]
    public void AddUser(string id, [FromBody]User user)
    {

    }
}

Where User Model is defined as

public class User
{
    [NoLog]
    public string Id { get; set; }

    public string Name { get; set; }

    public DateTime AnneviseryDate { get; set; }

    [NoLog]
    public int LinkId { get; set; }

    public List<Address> Addresses { get; set; }
}

public class Address
{
    public string AddressLine { get; set; }

    [NoLog]
    public string City { get; set; }

    [NoLog]
    public string Country { get; set; }
}

So when API is invoked by Swagger tool

enter image description here

The jsonBody is logged in Request without sensitive data. All sensitive data is replaced by 'PII Data' string literal.

enter image description here

Canady answered 14/12, 2018 at 9:5 Comment(4)
actually you could also use context.HttpContext.Features.Get<RequestTelemetry>() to get the Telemetry Instance in the ActionFilterAttributeCanicular
@Canicular any ideas as to why context.HttpContext.Features.Get<RequestTelemetry>() would be null in prod, the keys are correct and work in Dev and QA environment just prod is iffy but its the same code.Shimmery
Works just fine in .NET 5, but you dont need to provide PIITelemetryInitializer , because its already provided by doing services.AddApplicationInsightsTelemetry() in httpContext.Features.Get<RequestTelemetry>()Hailstorm
@Shimmery did you add services.AddApplicationInsights() in your Startup?Hailstorm
G
3

I never got @yonisha's answer working so I used a DelegatingHandler instead:

public class MessageTracingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Trace the request
        await TraceRequest(request);

        // Execute the request
        var response = await base.SendAsync(request, cancellationToken);

        // Trace the response
        await TraceResponse(response);

        return response;
    }

    private async Task TraceRequest(HttpRequestMessage request)
    {
        try
        {
            var requestTelemetry = HttpContext.Current?.GetRequestTelemetry();

            var requestTraceInfo = request.Content != null ? await request.Content.ReadAsByteArrayAsync() : null;

            var body = requestTraceInfo.ToString();

            if (!string.IsNullOrWhiteSpace(body) && requestTelemetry != null)
            {
                requestTelemetry.Properties.Add("Request Body", body);
            }
        }
        catch (Exception exception)
        {
            // Log exception
        }
    }

    private async Task TraceResponse(HttpResponseMessage response)
    {
        try
        {
            var requestTelemetry = HttpContext.Current?.GetRequestTelemetry();

            var responseTraceInfo = response.Content != null ? await response.Content.ReadAsByteArrayAsync() : null;

            var body = responseTraceInfo.ToString();

            if (!string.IsNullOrWhiteSpace(body) && requestTelemetry != null)
            {
                requestTelemetry.Properties.Add("Response Body", body); 
            }
        }
        catch (Exception exception)
        {
            // Log exception
        }
    }
}

.GetRequestTelemetry() is an extension method from Microsoft.ApplicationInsights.Web.

Garrotte answered 23/4, 2018 at 11:46 Comment(1)
Many thanks to you to add this solution, I have a web API project made in .net framework and I was searching on g oo gle to find the solution and I found this your post here, I added the same code and I am able to logs responses of web API requests, Please let me know if there is any update on this code or improvements?Pesce
H
3

In Asp.Net core it looks like we dont have to use ITelemetryInitializer. We can use the middleware to log the requests to application insights. Thanks to @IanKemp https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/686

 public async Task Invoke(HttpContext httpContext)
    {
        var requestTelemetry = httpContext.Features.Get<RequestTelemetry>();

        //Handle Request 
        var request = httpContext.Request;
        if (request?.Body?.CanRead == true)
        {
            request.EnableBuffering();

            var bodySize = (int)(request.ContentLength ?? request.Body.Length);
            if (bodySize > 0)
            {
                request.Body.Position = 0;

                byte[] body;

                using (var ms = new MemoryStream(bodySize))
                {
                    await request.Body.CopyToAsync(ms);

                    body = ms.ToArray();
                }

                request.Body.Position = 0;

                if (requestTelemetry != null)
                {
                    var requestBodyString = Encoding.UTF8.GetString(body);

                    requestTelemetry.Properties.Add("RequestBody", requestBodyString);
                }
            }
        }

        await _next(httpContext); // calling next middleware
    }
Harts answered 25/2, 2020 at 0:24 Comment(4)
Where do you utilize this? And how do you add this method to the middleware?Complot
This: httpContext.Features.Get<RequestTelemetry>() return null for me, is there any addition configuration required?Experienced
@Ramūnas did you figure this out our dev and qa are fine but prod is nullShimmery
@Shimmery I am using different approach provided here:Experienced
L
2

I implemented a middleware for this,

Invoke method does,

 if (context.Request.Method == "POST" || context.Request.Method == "PUT")
        {
            var bodyStr = GetRequestBody(context);
            var telemetryClient = new TelemetryClient();
            var traceTelemetry = new TraceTelemetry
            {
                Message = bodyStr,
                SeverityLevel = SeverityLevel.Verbose
            };
            //Send a trace message for display in Diagnostic Search. 
            telemetryClient.TrackTrace(traceTelemetry);
        }

Where, GetRequestBody is like,

private static string GetRequestBody(HttpContext context)
    {
        var bodyStr = "";
        var req = context.Request;

        //Allows using several time the stream in ASP.Net Core.
        req.EnableRewind();

        //Important: keep stream opened to read when handling the request.
        using (var reader = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
        {
            bodyStr = reader.ReadToEnd();
        }

        // Rewind, so the core is not lost when it looks the body for the request.
        req.Body.Position = 0;
        return bodyStr;
    }
Leavelle answered 9/3, 2017 at 6:11 Comment(6)
Where does the req.EnableRewind() method come from?Unknowing
In Asp.Net core "Microsoft.AspNetCore.Http.Internal.BufferingHelper" has this extension method for HttpRequest. Refer- learn.microsoft.com/en-us/aspnet/core/api/…Leavelle
it is a complicated solution considering you can use the SDK capabilities for such cases. Moreover, you'll need to correlate the trace telemetry to the relevant request telemetryGloat
@yonisha: what do you mean by "SDK capabilities"?Leavelle
@Dhanuka777, see my answer above. The SDK provides extensibility options for such cases, like telemetry initializers. See example in my answerGloat
This gets you the values posted but not the form variables, correct?Drover
V
1

I can able to log the request message body in Application Insights using @yonisha method but I can't able to log the response message body. I am interested in logging the response message body. I am already logging the Post, Put, Delete Request message body using @yonisha method.

When I tried to access the response body in the TelemetryInitializer I keep getting an exception with an error message saying that "stream was not readable. When I researched more I found that AzureInitializer is running as part of HttpModule(ApplicationInsightsWebTracking) so by the time it gets control response object is disposed.

I got an idea from @Oskar answer. Why not have a delegate handler and record the response since the response object is not disposed at the stage of message handler. The message handler is part of the Web API life cycle i.e. similar to the HTTP module but confined to web API. When I developed and tested this idea, fortunately, It worked I recorded the response in the request message using message handler and retrieved it at the AzureInitializer (HTTP module whose execution happens later than the message handler). Here is the sample code.

public class AzureRequestResponseInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        var requestTelemetry = telemetry as RequestTelemetry;
        if (requestTelemetry != null && HttpContext.Current != null && HttpContext.Current.Request != null)
        {
            if ((HttpContext.Current.Request.HttpMethod == HttpMethod.Post.ToString() 
                 || HttpContext.Current.Request.HttpMethod == HttpMethod.Put.ToString()) &&
                HttpContext.Current.Request.Url.AbsoluteUri.Contains("api"))
                using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
                {
                    HttpContext.Current.Request.InputStream.Position = 0;
                    string requestBody = reader.ReadToEnd();
                    if (requestTelemetry.Properties.Keys.Contains("requestbody"))
                    {
                        requestTelemetry.Properties["requestbody"] = requestBody;
                    }
                    else
                    {
                        requestTelemetry.Properties.Add("requestbody", requestBody);
                    }
                }
            else if (HttpContext.Current.Request.HttpMethod == HttpMethod.Get.ToString() 
                     && HttpContext.Current.Response.ContentType.Contains("application/json"))
            {
                var netHttpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
                if (netHttpRequestMessage.Properties.Keys.Contains("responsejson"))
                {
                    var responseJson = netHttpRequestMessage.Properties["responsejson"].ToString();
                    if (requestTelemetry.Properties.Keys.Contains("responsebody"))
                    {
                        requestTelemetry.Properties["responsebody"] = responseJson;
                    }
                    else
                    {
                        requestTelemetry.Properties.Add("responsebody", responseJson);
                    }
                }
            }
        }

    }
}

config.MessageHandlers.Add(new LoggingHandler());

public class LoggingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            var response = task.Result;
            StoreResponse(response);
            return response;
        });
    }


    private void StoreResponse(HttpResponseMessage response)
    {
        var request = response.RequestMessage;

        (response.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
        {
            var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper;

            if (request.Properties.ContainsKey("responseJson"))
            {
                request.Properties["responsejson"] = x.Result;
            }
            else
            {
                request.Properties.Add("responsejson", x.Result);
            }
        });
    }
}
Vendible answered 17/8, 2019 at 1:7 Comment(0)
C
0

The solution provided by yonisha is clean, but it does not work for me in .Net Core 2.0. This works if you have a JSON body:

public IActionResult MyAction ([FromBody] PayloadObject payloadObject)
{
    //create a dictionary to store the json string
    var customDataDict = new Dictionary<string, string>();

    //convert the object to a json string
    string activationRequestJson = JsonConvert.SerializeObject(
    new
    {
        payloadObject = payloadObject
    });

    customDataDict.Add("body", activationRequestJson);

    //Track this event, with the json string, in Application Insights
    telemetryClient.TrackEvent("MyAction", customDataDict);

    return Ok();
}
Corned answered 6/10, 2017 at 14:32 Comment(0)
A
0

I am sorry, @yonisha's solution does not seem to work in .NET 4.7. The Application Insights part works OK, but there is actually no simple way to get the request body inside the telemetry initializer in .NET 4.7. .NET 4.7 uses GetBufferlessInputStream() to get the stream, and this stream is "read once". One potential code is like this:

private static void LogRequestBody(ISupportProperties requestTelemetry)
{
    var requestStream = HttpContext.Current?.Request?.GetBufferlessInputStream();

    if (requestStream?.Length > 0)
        using (var reader = new StreamReader(requestStream))
        {
            string body = reader.ReadToEnd();
            requestTelemetry.Properties["body"] = body.Substring(0, Math.Min(body.Length, 8192));
        }
}

But the return from GetBufferlessInputStream() is already consumed, and does not support seeking. Therefore, the body will always be an empty string.

Assert answered 7/2, 2018 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.