using MailKit;

namespace Dpz.Core.Service;

public sealed class SerilogProtocolLogger(ILogger<SerilogProtocolLogger> logger) : IProtocolLogger
{
    private readonly EventId _eventId = new(10001, "邮件日志");

    private static readonly byte[] Secret = "********"u8.ToArray();

    private static readonly byte[] DefaultClientPrefix = "Client: "u8.ToArray();
    private static readonly byte[] DefaultServerPrefix = "Server: "u8.ToArray();

    private bool _clientMidline;
    private bool _serverMidline;

    private readonly MemoryStream _stream = new();

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _stream.Flush();
                _stream.Position = 0;
                using var reader = new StreamReader(_stream);
                var message = reader.ReadToEnd();
                logger.LogInformation(_eventId, message);
                _stream.Dispose();
            }

            _disposed = true;
        }
    }

    ~SerilogProtocolLogger() => Dispose(false);

    public void LogConnect(Uri uri)
    {
        var bytes = Encoding.ASCII.GetBytes($"{DateTime.Now} Connected to {uri}\r\n");
        if (_clientMidline || _serverMidline)
        {
            _clientMidline = false;
            _serverMidline = false;
        }

        _stream.Write(bytes, 0, bytes.Length);
        _stream.Flush();
    }

    public void LogClient(byte[]? buffer, int offset, int count)
    {
        LogBuffer(buffer, offset, count, DefaultClientPrefix, ref _clientMidline, true);
    }

    public void LogServer(byte[]? buffer, int offset, int count)
    {
        LogBuffer(buffer, offset, count, DefaultServerPrefix, ref _serverMidline, false);
    }

    private void LogBuffer(
        byte[]? buffer,
        int offset,
        int count,
        byte[] prefix,
        ref bool midline,
        bool isClient
    )
    {
        if (buffer == null)
        {
            logger.LogError(_eventId, "buffer is null");
            return;
        }

        if (offset < 0 || offset > buffer.Length)
        {
            logger.LogError(
                _eventId,
                "offset less than 0 or offset greater than {Offset}",
                buffer.Length
            );
            return;
        }

        if (count < 0 || count > buffer.Length - offset)
        {
            logger.LogError(
                _eventId,
                "count less than 0 or count greater than {Offset}",
                buffer.Length - offset
            );
            return;
        }

        Log(prefix, ref midline, buffer, offset, count, isClient);
    }

    private void Log(
        byte[] prefix,
        ref bool midline,
        byte[] buffer,
        int offset,
        int count,
        bool isClient
    )
    {
        var number = offset + count;
        var index = offset;
        while (index < number)
        {
            var offset1 = index;
            while (index < number && buffer[index] != 10)
            {
                ++index;
            }

            if (!midline)
            {
                _stream.Write(prefix, 0, prefix.Length);
            }

            if (index < number && buffer[index] == 10)
            {
                midline = false;
                ++index;
            }
            else
            {
                midline = true;
            }

            if (isClient)
            {
                var authenticationSecrets = AuthenticationSecretDetector?.DetectSecrets(
                    buffer,
                    offset1,
                    index - offset1
                );
                if (authenticationSecrets != null)
                {
                    foreach (var detectSecret in authenticationSecrets)
                    {
                        if (detectSecret.StartIndex > offset1)
                        {
                            _stream.Write(buffer, offset1, detectSecret.StartIndex - offset1);
                        }

                        offset1 = detectSecret.StartIndex + detectSecret.Length;
                        _stream.Write(Secret, 0, Secret.Length);
                    }
                }
            }

            _stream.Write(buffer, offset1, index - offset1);
        }
    }

    public IAuthenticationSecretDetector? AuthenticationSecretDetector { get; set; }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个实现了 MailKit 的 IProtocolLogger 接口的邮件协议日志记录器类,主要用于记录邮件客户端和服务器之间的通信过程。以下是详细解释:

类的基本结构

public sealed class SerilogProtocolLogger(ILogger<SerilogProtocolLogger> logger) : IProtocolLogger
  • 使用 C# 12 的主构造函数语法
  • 实现了 IProtocolLogger 接口
  • 通过依赖注入接收一个 ILogger 实例

核心字段和属性

private readonly EventId _eventId = new(10001, "邮件日志");
private static readonly byte[] Secret = "********"u8.ToArray();
private static readonly byte[] DefaultClientPrefix = "Client: "u8.ToArray();
private static readonly byte[] DefaultServerPrefix = "Server: "u8.ToArray();
  • _eventId: 用于标识日志事件的ID和名称
  • Secret: 用于替换敏感信息的占位符
  • 前缀字节数组:用于区分客户端和服务器的消息

主要功能方法

1. 连接日志记录

public void LogConnect(Uri uri)

记录与邮件服务器的连接信息,包括时间戳和URI。

2. 客户端/服务器消息记录

public void LogClient(byte[]? buffer, int offset, int count)
public void LogServer(byte[]? buffer, int offset, int count)

分别记录客户端发送和服务器接收的数据。

3. 核心日志处理逻辑

private void Log(byte[] prefix, ref bool midline, byte[] buffer, int offset, int count, bool isClient)

这个方法处理实际的日志写入:

  • 按行分割数据(以换行符 \n 为分隔符)
  • 为每行添加适当的前缀("Client: " 或 "Server: ")
  • 处理跨行的数据(通过 midline 标志)

安全特性

public IAuthenticationSecretDetector? AuthenticationSecretDetector { get; set; }
  • 支持检测和隐藏认证敏感信息
  • 当检测到敏感数据时,用 Secret("********")替换实际内容
  • 只对客户端数据进行敏感信息检测

资源管理

实现了完整的 Dispose 模式:

public void Dispose()
private void Dispose(bool disposing)
~SerilogProtocolLogger()
  • 在释放时将缓冲的日志内容写入到 Serilog
  • 使用 MemoryStream 作为临时缓冲区
  • 确保资源正确释放

使用场景

这个类主要用于:

  1. 邮件协议调试: 记录 SMTP、POP3、IMAP 等协议的通信过程
  2. 问题排查: 帮助开发者分析邮件发送/接收过程中的问题
  3. 安全审计: 记录邮件通信日志,同时保护敏感认证信息

设计亮点

  1. 安全性: 自动检测和隐藏认证敏感信息
  2. 可读性: 为客户端和服务器消息添加清晰的前缀
  3. 性能: 使用 MemoryStream 缓冲,减少频繁的日志写入
  4. 集成性: 与 Serilog 日志框架完美集成

这是一个设计良好的邮件协议日志记录器,既保证了调试的便利性,又考虑了安全性和性能。

评论加载中...