You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6中Serilog CorrelationId在邮件与追踪像素的复用问题

解决方案:邮件发送与追踪日志共享CorrelationId

核心问题是邮件发送请求和用户打开邮件触发的追踪像素请求是两个完全独立的HTTP请求,默认的CorrelationId中间件会为每个请求生成新ID。要实现共享,需要在发送邮件时生成一个专属的关联ID,将其嵌入追踪像素的URL参数中,在追踪请求时提取该ID并注入日志上下文。

步骤1:生成邮件专属关联ID并嵌入追踪URL

修改MailService,生成唯一的MessageCorrelationId,将其作为参数附加到追踪像素的src地址中:

using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Options;
using MimeKit;
using MimeKit.Text;
using DAL.Model;
using ImageTracker.Service;

namespace SimpleEmailApp.Services
{
    public class MailService : IMailService
    {
        private readonly AppSetting _mailSetting;
        private readonly ILogger<MailService> _logger;
        private readonly IWhichMessageIsRead _whichMessageIsRead;

        public MailService(IOptions<AppSetting> mailSetting, ILogger<MailService> logger,
                           IWhichMessageIsRead whichMessageIsRead)
        {
            _mailSetting = mailSetting.Value;
            _logger = logger;
            _whichMessageIsRead = whichMessageIsRead;
        }

        public async Task SendEmailAsync(EmailMessage message)
        {
            var email = new MimeMessage();
            // 生成专属的邮件关联ID
            var messageCorrelationId = Guid.NewGuid().ToString();

            try
            {
                email.Sender = MailboxAddress.Parse(_mailSetting.Mail);
                email.To.Add(MailboxAddress.Parse(message.Reciver));
                email.Subject = message.Subject;

                // 将关联ID嵌入追踪像素的URL参数中
                var imgTag = string.Format(@"<img src=""https://localhost:7156/api/Mail?correlationId={0}"" alt="""" width=""1"" height=""1"" 
                                style=""width: 0px; height: 0px; border:0px;""/>", messageCorrelationId);

                email.Body = new TextPart(TextFormat.Html)
                {
                    Text = message.Body + "         " + imgTag
                };

                // 使用该ID记录邮件发送日志
                using (_logger.BeginScope("{@CorrelationId}", messageCorrelationId))
                {
                    _logger.LogInformation("Email {@mailRequest} created for {Sender} at {now}.", message, _mailSetting.Mail, DateTime.Now);
                    _whichMessageIsRead.GetMessage(messageCorrelationId, message);
                }
            }
            catch (Exception)
            {
                _logger.LogError("Email is not sent.");
                throw;
            }

            using var smtp = new SmtpClient();
            try
            {
                smtp.Connect(_mailSetting.Host, _mailSetting.Port, SecureSocketOptions.StartTls);
                smtp.Authenticate(_mailSetting.Mail, _mailSetting.Password);
                await smtp.SendAsync(email);
                smtp.Disconnect(true);
            }
            catch (Exception)
            {
                _logger.LogError("SMTP Server Configuration Error occurred");
                throw;
            }
        }
    }
}

步骤2:修改中间件支持从URL参数读取关联ID

更新LogHeaderMiddleware,优先从URL参数correlationId读取,其次读取请求头X-SessionId,确保追踪请求能复用邮件发送时的ID:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CorrelationProvider.Middleware
{
    public class LogHeaderMiddleware
    {
        private readonly RequestDelegate _next;

        public LogHeaderMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            string correlationId = null;

            // 先从URL参数获取关联ID(追踪像素请求用)
            if (context.Request.Query.TryGetValue("correlationId", out var queryId))
            {
                correlationId = queryId;
            }
            // 再从请求头获取(普通API请求用)
            else if (context.Request.Headers.TryGetValue("X-SessionId", out var headerId))
            {
                correlationId = headerId;
            }
            // 如果都没有,生成新ID(可选,保证日志总有CorrelationId)
            else
            {
                correlationId = Guid.NewGuid().ToString();
            }

            var logger = context.RequestServices.GetRequiredService<ILogger<LogHeaderMiddleware>>();
            using (logger.BeginScope("{@CorrelationId}", correlationId))
            {
                // 将ID存入HttpContext,方便其他服务获取
                context.Items["CorrelationId"] = correlationId;
                await _next(context);
            }
        }
    }
}

步骤3:调整追踪API的日志逻辑

在处理追踪像素的MailController中,直接复用上下文里的CorrelationId记录日志:

[ApiController]
[Route("api/[controller]")]
public class MailController : ControllerBase
{
    private readonly ILogger<MailController> _logger;

    public MailController(ILogger<MailController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult TrackEmail()
    {
        // 从HttpContext获取关联ID
        var correlationId = HttpContext.Items["CorrelationId"]?.ToString();
        
        // 记录邮件打开日志
        _logger.LogInformation("Email with CorrelationId {CorrelationId} was opened at {now}", correlationId, DateTime.Now);

        // 返回1x1透明像素
        var pixel = Convert.FromBase64String("R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==");
        return File(pixel, "image/gif");
    }
}

关键说明

  • 邮件发送时生成的messageCorrelationId是绑定到单封邮件的唯一标识,通过URL参数传递给追踪请求
  • 中间件统一处理关联ID的来源,保证日志上下文一致
  • 所有相关日志(发送、打开)都会携带同一个CorrelationId,方便后续排查和统计

内容的提问来源于stack exchange,提问作者حسین محمدی

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 09:20:24