How to pass Serilog correlationId from HTTP request header to grpc call
shashankvivek opened this issue · 2 comments
I want to have a correlationId that can be used to track calls across different micro services.
Here is what I am aiming for:
- UI client calling BE (MicroService-A) with correlation-id.
- MicroService-A uses this correlation-id and adds it to the Header of Grpc Metadata to call MicroService-B. If not there, serilog generates it automatically using Serilog enricher client-info
.Enrich.WithCorrelationId(headerName: "correlation-id", addValueIfHeaderAbsence: true)
- MicroService-B add this to serilog using
LogContext
.
I have been able to achieve point 1 and 3. For point 2, I am not sure how to get the correlation-id
from HttpContext into my Grpc Interceptor.
Here is what I have done.
Client interceptor (MicroService-A):
public class GrpcClientLoggerInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var metadata = new Metadata();
metadata
.Add("x-correlation-id", "http-request-id"); // <-- This is where I need to get from
//current HTTP context and set into Grpc metadata.
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method,
context.Host,
context.Options.WithHeaders(
(context.Options.Headers ?? new Metadata()).Aggregate(
metadata,
AddIfNonExistent))
);
return continuation(request, newContext);
}
private static Metadata AddIfNonExistent(Metadata metadata, Metadata.Entry entry)
{
if (metadata.Get(entry.Key) == null) metadata.Add(entry);
return metadata;
}
}
Server interceptor (MicroService-B)
public class GrpcServerLoggerInterceptorBase : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
var correlationIdHeader = context.RequestHeaders.FirstOrDefault(m => String.Equals(m.Key, "x-correlation-id", StringComparison.Ordinal));
var correlationId = correlationIdHeader?.Value == null ? new Guid().ToString() : correlationIdHeader?.Value;
using (LogContext.PushProperty("x-correlation-id", correlationId))
{
return await base.UnaryServerHandler(request, context, continuation);
}
}
}
What is the best way to access correlation-id from serilog and get the "x-correlation-id"
inside my Client Grpc interceptor? How can I get HttpContext here
An async local is easiest. Either define your own, or use Activity, or use IHttpContextAccessor.
https://github.com/grpc/grpc-dotnet/blob/master/src/Grpc.AspNetCore.Server.ClientFactory/ContextPropagationInterceptor.cs is an example of using IHttpContextAccessor. You would get and set headers rather than the cancellation token.
Thanks @JamesNK .
For others, I made below changes:
- I used
InterceptorRegistrations
in place ofInterceptor
because it is now deprecated. This gave me access toIServiceProvider
opts.InterceptorRegistrations.Add( new InterceptorRegistration(
InterceptorScope.Client,
s =>
{
var accessor = s.GetRequiredService<IHttpContextAccessor>();
return new GrpcClientLoggerInterceptor(accessor);
}));
- Then I injected
IHttpContextAccessor
in my interceptor:
private readonly IHttpContextAccessor httpContext;
public GrpcClientLoggerInterceptor(IHttpContextAccessor httpContext)
{
this.httpContext = httpContext;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var correlationId = httpContext?.HttpContext?.Request.Headers["x-correlation-id"] ?? new Guid().ToString();
var metadata = new Metadata
{
{ "x-correlation-id", correlationId}
};
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method,
context.Host,
context.Options.WithHeaders(
(context.Options.Headers ?? new Metadata()).Aggregate(
metadata,
AddIfNonExistent))
);
return continuation(request, newContext);
}
private static Metadata AddIfNonExistent(Metadata metadata, Metadata.Entry entry)
{
if (metadata.Get(entry.Key) == null) metadata.Add(entry);
return metadata;
}