mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
request telemetry, fixed usage logging bug
This commit is contained in:
@@ -1 +1 @@
|
||||
685
|
||||
687
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using AutopilotMonitor.Functions.Extensions;
|
||||
using AutopilotMonitor.Functions.Helpers;
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
|
||||
@@ -232,10 +233,9 @@ public class AuthenticationMiddleware : IFunctionsWorkerMiddleware
|
||||
authenticated = true;
|
||||
|
||||
// Track per-user usage (fire-and-forget via Task.Run to ensure execution in isolated worker)
|
||||
var oid = principal.FindFirst("oid")?.Value;
|
||||
var upn = principal.FindFirst("upn")?.Value
|
||||
?? principal.FindFirst("preferred_username")?.Value;
|
||||
var tid = principal.FindFirst("tid")?.Value;
|
||||
var oid = principal.GetObjectId();
|
||||
var upn = principal.GetUserPrincipalName();
|
||||
var tid = principal.GetTenantId();
|
||||
if (!string.IsNullOrEmpty(oid))
|
||||
{
|
||||
var normalizedEndpoint = EndpointNormalizer.Normalize(requestPath);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Middleware;
|
||||
using AutopilotMonitor.Functions.Helpers;
|
||||
|
||||
namespace AutopilotMonitor.Functions.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Emits worker-side <see cref="RequestTelemetry"/> with business context (TenantId, UserId,
|
||||
/// CorrelationId, UserRole) so the Application Insights <c>requests</c> table is queryable
|
||||
/// by tenant, user, and correlation ID. Runs first in the pipeline to capture accurate
|
||||
/// duration including auth and policy evaluation. Non-HTTP triggers are skipped.
|
||||
/// </summary>
|
||||
public class RequestTelemetryMiddleware : IFunctionsWorkerMiddleware
|
||||
{
|
||||
private readonly TelemetryClient _telemetryClient;
|
||||
|
||||
public RequestTelemetryMiddleware(TelemetryClient telemetryClient)
|
||||
{
|
||||
_telemetryClient = telemetryClient;
|
||||
}
|
||||
|
||||
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
|
||||
{
|
||||
var httpContext = context.GetHttpContext();
|
||||
if (httpContext == null)
|
||||
{
|
||||
// Non-HTTP trigger (timer, queue) — nothing to track
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
Exception? caughtException = null;
|
||||
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
caughtException = ex;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
sw.Stop();
|
||||
|
||||
var statusCode = caughtException != null ? 500 : httpContext.Response.StatusCode;
|
||||
var functionName = context.FunctionDefinition.Name;
|
||||
var method = httpContext.Request.Method;
|
||||
var url = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}{httpContext.Request.Path}{httpContext.Request.QueryString}";
|
||||
|
||||
var requestTelemetry = new RequestTelemetry
|
||||
{
|
||||
Name = $"{method} {functionName}",
|
||||
Timestamp = startTime,
|
||||
Duration = sw.Elapsed,
|
||||
ResponseCode = statusCode.ToString(),
|
||||
Success = statusCode < 500,
|
||||
Url = new Uri(url),
|
||||
};
|
||||
|
||||
// Distributed trace correlation
|
||||
var activity = Activity.Current;
|
||||
if (activity != null)
|
||||
{
|
||||
requestTelemetry.Context.Operation.Id = activity.RootId;
|
||||
requestTelemetry.Context.Operation.ParentId = activity.Id;
|
||||
}
|
||||
|
||||
// Business context from downstream middleware
|
||||
requestTelemetry.Properties["Source"] = "WorkerMiddleware";
|
||||
requestTelemetry.Properties["FunctionName"] = functionName;
|
||||
requestTelemetry.Properties["HttpMethod"] = method;
|
||||
requestTelemetry.Properties["HttpPath"] = httpContext.Request.Path.Value ?? "";
|
||||
|
||||
if (context.Items.TryGetValue("CorrelationId", out var corrId) && corrId is string correlationId)
|
||||
requestTelemetry.Properties["CorrelationId"] = correlationId;
|
||||
|
||||
var reqCtx = context.GetRequestContext();
|
||||
if (!string.IsNullOrEmpty(reqCtx.TenantId))
|
||||
requestTelemetry.Properties["TenantId"] = reqCtx.TenantId;
|
||||
if (!string.IsNullOrEmpty(reqCtx.UserPrincipalName))
|
||||
requestTelemetry.Properties["UserId"] = reqCtx.UserPrincipalName;
|
||||
if (!string.IsNullOrEmpty(reqCtx.UserRole))
|
||||
requestTelemetry.Properties["UserRole"] = reqCtx.UserRole;
|
||||
|
||||
if (caughtException != null)
|
||||
requestTelemetry.Properties["ExceptionType"] = caughtException.GetType().Name;
|
||||
|
||||
_telemetryClient.TrackRequest(requestTelemetry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ var builder = FunctionsApplication.CreateBuilder(args);
|
||||
builder.ConfigureFunctionsWebApplication();
|
||||
|
||||
// Register middleware pipeline (Azure Functions .NET 8 isolated worker pattern)
|
||||
// Order matters: correlation ID (all requests) → JWT authentication (401) → policy enforcement (403)
|
||||
// Order matters: request telemetry (wraps all) → correlation ID → JWT authentication (401) → policy enforcement (403)
|
||||
builder.UseMiddleware<RequestTelemetryMiddleware>();
|
||||
builder.UseMiddleware<CorrelationIdMiddleware>();
|
||||
builder.UseMiddleware<AuthenticationMiddleware>();
|
||||
builder.UseMiddleware<PolicyEnforcementMiddleware>();
|
||||
|
||||
Reference in New Issue
Block a user