问题
We are using Owin on Azure for a REST service, and have to report to Application Insights directly. We want to log exceptions and requests. Right now we have this:
using AppFunc = Func<IDictionary<string, object>, Task>;
public class InsightsReportMiddleware
{
readonly AppFunc next;
readonly TelemetryClient telemetryClient;
public InsightsReportMiddleware(AppFunc next, TelemetryClient telemetryClient)
{
if (next == null)
{
throw new ArgumentNullException("next");
}
this.telemetryClient = telemetryClient;
this.next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
var sw = new Stopwatch();
sw.Start();
await next(environment);
sw.Stop();
var ctx = new OwinContext(environment);
var rt = new RequestTelemetry(
name: ctx.Request.Path.ToString(),
timestamp: DateTimeOffset.Now,
duration: sw.Elapsed,
responseCode: ctx.Response.StatusCode.ToString(),
success: 200 == ctx.Response.StatusCode
);
rt.Url = ctx.Request.Uri;
rt.HttpMethod = ctx.Request.Method;
telemetryClient.TrackRequest(rt);
}
}
public class InsightsExceptionLogger : ExceptionLogger
{
readonly TelemetryClient telemetryClient;
public InsightsExceptionLogger(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public override Task LogAsync(ExceptionLoggerContext context, System.Threading.CancellationToken cancellationToken)
{
telemetryClient.TrackException(context.Exception);
return Task.FromResult<object>(null);
}
public override void Log(ExceptionLoggerContext context)
{
telemetryClient.TrackException(context.Exception);
}
}
They are registered to our application like so:
static void ConfigureInsights(IAppBuilder app, HttpConfiguration config)
{
var rtClient = new TelemetryClient();
app.Use<InsightsReportMiddleware>(rtClient);
config.Services.Add(typeof (IExceptionLogger), new InsightsExceptionLogger(rtClient));
}
This works, except exceptions and requests are not connected. Both get logged, but when clicking on a failed request it says "No related exceptions were found". Conversely, when opening an exception properties, we can read "Requests affected by this exception: 0". What is the proper way to do that?
回答1:
Application Insights links exceptions and requests by comparing ExceptionTelemetry.Context.Operation.Id
and RequestTelemetry.Id
.
I don't have a code sample for OWIN, however the ASP.NET 5 package of the Application Insights SDK has similar middleware components for tracking exceptions and requests. I hope you can use this information to build a solution for OWIN.
We create a RequestTelemetry
instance and store it in the request processing environment before invoking the next middleware component which performs actual request processing. In ASP.NET 5, we register RequestTelemetry as a request-scoped service. With OWIN, I'd imagine your middleware component would create it and store it in the environment
dictionary.
We also have an ITelemetryInitializer
, called OperationIdTelemetryInitializer, that sets the ITelemetry.Context.Operation.Id
with the RequestTelemetry.Id
extracted from the environment. This initializer needs to be added to the TelemetryConfiguration
used to create the TelemetryClient
instances in your application. TelemetryConfiguration.Active
is used by default.
回答2:
What I ended-up doing:
using AppFunc = Func<IDictionary<string, object>, Task>;
public class InsightsReportMiddleware
{
readonly AppFunc next;
readonly TelemetryClient telemetryClient;
public InsightsReportMiddleware(AppFunc next, TelemetryClient telemetryClient)
{
if (next == null)
{
throw new ArgumentNullException("next");
}
this.telemetryClient = telemetryClient;
this.next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
var ctx = new OwinContext(environment);
var rt = new RequestTelemetry()
{
Url = ctx.Request.Uri,
HttpMethod = ctx.Request.Method,
Name = ctx.Request.Path.ToString(),
Timestamp = DateTimeOffset.Now
};
environment.Add("requestTelemetry", rt);
var sw = new Stopwatch();
sw.Start();
await next(environment);
sw.Stop();
rt.ResponseCode = ctx.Response.StatusCode.ToString();
rt.Success = ctx.Response.StatusCode < 400;
rt.Duration = sw.Elapsed;
telemetryClient.TrackRequest(rt);
}
}
public class InsightsExceptionLogger : ExceptionLogger
{
readonly TelemetryClient telemetryClient;
public InsightsExceptionLogger(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public override Task LogAsync(ExceptionLoggerContext context, System.Threading.CancellationToken cancellationToken)
{
var owinContext = context.Request.GetOwinEnvironment();
ExceptionTelemetry exceptionTelemetry = null;
if (owinContext != null)
{
object obj;
if (owinContext.TryGetValue("requestTelemetry", out obj))
{
var requestTelemetry = obj as RequestTelemetry;
exceptionTelemetry = new ExceptionTelemetry(context.Exception)
{
Timestamp = DateTimeOffset.Now
};
exceptionTelemetry.Context.Operation.Id = requestTelemetry.Id;
}
}
if (exceptionTelemetry != null)
{
telemetryClient.TrackException(exceptionTelemetry);
}
else
{
telemetryClient.TrackException(context.Exception);
}
return Task.FromResult<object>(null);
}
public override void Log(ExceptionLoggerContext context)
{
telemetryClient.TrackException(context.Exception);
}
}
回答3:
For my current client, we're not yet OWIN-ed up.
I registered a DelegatingHandler
with WebAPI that sticks the current request into the thread's SynchronizationContext
via CallContext.LogicalSetData
and removes it after the request is complete.
In my existing logging system, which had to be retro-fitted with the Application Insights stuff, I then grab the request from the thread via CallContext.LogicalSetData
and set about getting the HttpContext
which is placed into the request properties by the framework, then from the HttpContext.Items
, I get the RequestTelemetry
instance.
Ultimately, this is all needed because I can't get access to the request or action or anything from the IoC container that's newing-up the services.
Eventually we might rewrite some of this to allow improved creation and flowing of an OperationContext
or InstrumentationContext
style object down the stack and get rid of the handler and CallContext
funny business.
回答4:
There is an overload of TelemetryClient.TrackException
method that accepts dictionary of properties. It is designed specifically to clasify and search for the exception. This allows to generate an error Id, and link the error to AppInsights.
An example of error handling:
var errorId = GenerateErrorId();
var trackProperties = new Dictionary<string, string>();
trackProperties.Add("ErrorId", errorId);
var ai = new TelemetryClient();
ai.TrackException(exception, trackProperties);
JObject resp = new JObject();
resp["message"] = exception.Message + " - " + errorId;
await context.Response.WriteAsync(resp.ToString());
来源:https://stackoverflow.com/questions/30485385/how-to-link-exceptions-to-requests-in-application-insights-on-azure