Reputation: 837
I am providing my endpoint with a correlation ID:
I then read that ID from the HttpContext.Request.Headers and use that as my telemetry.Context.Operation.Id.
This works, but when I look in my log I have an extra entry which is auto generated by the framework. This entry has its own ID. How can I ensure the framework useses the same ID?
This is how I configure the service
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Users.Api.Services;
using Users.Api.Utility;
using Users.Services.Implementations;
using Users.Services.Interfaces;
using Users.Sql;
using Users.Utility;
using Packages.Api.Filters;
using Packages.Audit;
using Swashbuckle.AspNetCore.Swagger;
namespace Users.Api
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Dependency injection
services.AddAutoMapper();
services.AddDbContext<UsersContext>(
builder => builder.UseSqlServer(Environment.GetEnvironmentVariable("Connectionstring")));
services.AddScoped<IUserService, UserService>();
services.AddScoped<IIdentityService, IdentityService>();
services.AddScoped<IServiceBusCommunicator, ServiceBusCommunicator>();
services.AddScoped<IGraphClient, GraphClient>();
services.AddScoped<IClaimsHarvester, ClaimsHarvester>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddSingleton<HttpClient>();
services.AddSingleton<EndpointConfiguration>();
services.AddSingleton<GraphConfiguration>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IAuditLogClient, AuditLogClient>();
var clientId = Environment.GetEnvironmentVariable("Authentication:AzureAd:ClientId");
var tenant = Environment.GetEnvironmentVariable("Authentication:AzureAd:Tenant");
var signInPolicyId = Environment.GetEnvironmentVariable("Authentication:AzureAd:SignInPolicyId");
var authority = $"https://login.microsoftonline.com/tfp/{tenant}/{signInPolicyId}/v2.0/";
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(bearerOptions =>
{
bearerOptions.Authority = authority;
bearerOptions.Audience = clientId;
bearerOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
services.AddMvc();
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new Info { Title = "Users API", Version = "v1" });
});
services.ConfigureSwaggerGen(options =>
{
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
options.OperationFilter<CorrelationHeaderParameterOperationFilter>();
options.OperationFilter<XTotalCountHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
ContextInitializer contextInitializer)
{
if (env.IsDevelopment())
{
// loggerFactory.AddConsole(Configuration.GetSection("Logging"));
// loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint($"{Environment.GetEnvironmentVariable("ServiceFabric:UniqueUrlPath")}/swagger/v1/swagger.json", "Contacts API V1");
});
// Seed default values
contextInitializer.Seed();
}
private Task AuthenticationFailed(AuthenticationFailedContext arg)
{
// For debugging purposes only!
var s = $"AuthenticationFailed: {arg.Exception.Message}";
arg.Response.ContentLength = s.Length;
arg.Response.Body.Write(Encoding.UTF8.GetBytes(s), 0, s.Length);
return Task.FromResult(0);
}
}
}
Upvotes: 1
Views: 3325
Reputation: 351
This is not generally supported by ApplicationInsights.
You may still achieve it, but have to write a custom request collection.
You'd need to remove RequestTelemetryTrackingModule from the DI container and add your custom middleware that tracks requests.
What will not work with this approach (code is below):
scenarios when you use different instrumentation keys on this service and upstream services. You can check out how it is handled in AppInsights SDK ( set requestTelemetry.Source and response header)
correlation with informational traces emitted by AspNetCore.
normally, request telemetry name contains route rather than path, you might need to figure it out
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry("ikey");
var requestModule =
services.FirstOrDefault(sd => sd.ImplementationType == typeof(RequestTrackingTelemetryModule));
if (requestModule != null)
{
services.Remove(requestModule);
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, TelemetryClient client)
{
app.UseMiddleware<RequestMiddleware>(client);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
public class RequestMiddleware
{
private readonly RequestDelegate next;
private readonly TelemetryClient telemetryClient;
public RequestMiddleware(
RequestDelegate next,
TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var activity = new Activity("request");
if (context.Request.Headers.TryGetValue("x-my-correlation-id", out var val))
{
activity.SetParentId(val);
}
using (var request = telemetryClient.StartOperation<RequestTelemetry>(activity))
{
request.Telemetry.Url = context.Request.GetUri();
request.Telemetry.Context.Operation.Name = $"{context.Request.Method} {context.Request.Path.Value}";
request.Telemetry.Name = $"{context.Request.Method} {context.Request.Path.Value}";
try
{
await next.Invoke(context).ConfigureAwait(false);
}
catch (Exception e)
{
telemetryClient.TrackException(e);
request.Telemetry.Success = false;
throw;
}
finally
{
if (context.Response != null)
{
request.Telemetry.ResponseCode = context.Response.StatusCode.ToString();
request.Telemetry.Success = context.Response.StatusCode < 400;
}
else
{
request.Telemetry.Success = false;
}
}
}
}
}
Upvotes: 2