UNIVPLMDataIntegration/Admin.NET/Plugins/Admin.NET.Plugin.Ai/Handler/LoggingHandler.cs
2025-09-14 18:13:03 +08:00

211 lines
7.7 KiB
C#

// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Encodings.Web;
using System.Text.Json;
namespace Admin.NET.Plugin.Ai;
public class LoggingHandler : DelegatingHandler
{
private readonly ILogger<LoggingHandler> _logger;
public LoggingHandler(HttpMessageHandler innerHandler)
{
InnerHandler = innerHandler ?? new HttpClientHandler();
_logger = App.GetService<ILogger<LoggingHandler>>();
}
private string FormatJson(string json)
{
try
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var jsonElement = JsonSerializer.Deserialize<JsonElement>(json);
return JsonSerializer.Serialize(jsonElement, options);
}
catch
{
return json;
}
}
private string ConvertUnicodeToChinese(string text)
{
return Regex.Replace(text, @"\\u([0-9a-fA-F]{4})", match =>
{
return ((char)Convert.ToInt32(match.Groups[1].Value, 16)).ToString();
});
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var loggerContent = new StringBuilder();
loggerContent.AppendLine("=== HTTP Request ===");
loggerContent.AppendLine($"{request.Method} {request.RequestUri} HTTP/1.1");
foreach (var header in request.Headers)
{
loggerContent.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
if (request.Content != null)
{
var content = await request.Content.ReadAsStringAsync();
if (request.Content.Headers.ContentType?.MediaType?.Contains("json") == true)
{
content = FormatJson(content);
}
content = ConvertUnicodeToChinese(content);
loggerContent.AppendLine($"\n{content}");
}
loggerContent.AppendLine();
_logger.LogInformation("======== LLM请求开始 =========");
_logger.LogInformation(loggerContent.ToString());
_logger.LogInformation("======== LLM请求结束 =========");
var response = await base.SendAsync(request, cancellationToken);
loggerContent.Clear();
// 检查是否是流式响应
if (response.Content != null &&
response.Content.Headers.ContentType?.MediaType?.Contains("text/event-stream") == true)
{
// 记录响应头
loggerContent.AppendLine("=== HTTP Response (Stream) ===");
loggerContent.AppendLine($"HTTP/1.1 {(int)response.StatusCode} {response.StatusCode}");
foreach (var header in response.Headers)
{
loggerContent.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
_logger.LogInformation("======== LLM流式响应开始 =========");
_logger.LogInformation(loggerContent.ToString());
// 创建包装流来记录内容
var originalStream = await response.Content.ReadAsStreamAsync();
var loggingStream = new LoggingStream(originalStream, chunk =>
{
_logger.LogInformation($"流式响应内容: {chunk}");
});
// 创建新的响应内容
var newContent = new StreamContent(loggingStream);
foreach (var header in response.Content.Headers)
{
newContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
response.Content = newContent;
return response;
}
// 非流式响应的处理保持不变
loggerContent.AppendLine("=== HTTP Response ===");
loggerContent.AppendLine($"HTTP/1.1 {(int)response.StatusCode} {response.StatusCode}");
foreach (var header in response.Headers)
{
loggerContent.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
if (response.Content != null)
{
var content = await response.Content.ReadAsStringAsync();
if (response.Content.Headers.ContentType?.MediaType?.Contains("json") == true)
{
content = FormatJson(content);
}
content = ConvertUnicodeToChinese(content);
loggerContent.AppendLine($"\n{content}");
}
loggerContent.AppendLine();
_logger.LogInformation("======== LLM响应开始 =========");
_logger.LogInformation(loggerContent.ToString());
_logger.LogInformation("======== LLM响应结束 =========");
return response;
}
}
// 用于记录流内容的包装流
public class LoggingStream : Stream
{
private readonly Stream _innerStream;
private readonly Action<string> _logAction;
private readonly StringBuilder _buffer = new StringBuilder();
public LoggingStream(Stream innerStream, Action<string> logAction)
{
_innerStream = innerStream;
_logAction = logAction;
}
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => _innerStream.Length;
public override long Position
{
get => _innerStream.Position;
set => _innerStream.Position = value;
}
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = _innerStream.Read(buffer, offset, count);
if (bytesRead > 0)
{
var chunk = Encoding.UTF8.GetString(buffer, offset, bytesRead);
_buffer.Append(chunk);
// 检查是否是一个完整的SSE消息
if (chunk.Contains("\n\n") || chunk.Contains("\r\n\r\n"))
{
var message = _buffer.ToString().Trim();
_logAction(message);
_buffer.Clear();
}
}
return bytesRead;
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var bytesRead = await _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
if (bytesRead > 0)
{
var chunk = Encoding.UTF8.GetString(buffer, offset, bytesRead);
_buffer.Append(chunk);
// 检查是否是一个完整的SSE消息
if (chunk.Contains("\n\n") || chunk.Contains("\r\n\r\n"))
{
var message = _buffer.ToString().Trim();
_logAction(message);
_buffer.Clear();
}
}
return bytesRead;
}
public override void Flush() => _innerStream.Flush();
public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
public override void SetLength(long value) => _innerStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
{
_innerStream.Dispose();
}
base.Dispose(disposing);
}
}