在开发中,将系统日志集中管理并支持实时搜索是提升运维效率的重要手段。本文将介绍如何在 ASP.NET Core 项目中通过 HttpClient
与 Elasticsearch 进行集成,实现日志的自动记录、索引和查询功能。
技术架构概览
- • ASP.NET Core:构建 Web API 服务
- • HttpClient:与 Elasticsearch REST 接口通信
- • Elasticsearch:用于日志数据的存储与检索
- • 中间件(Middleware):拦截请求并记录日志
- • Basic Auth:保障 Elasticsearch 安全访问
一、配置文件设置
首先,在 appsettings.json
中配置 Elasticsearch 的地址和认证信息:
{
"Elasticsearch":{
"Url":"https://es-host:9200",
"Username":"username",
"Password":"password"
},
"Logging":{
"LogLevel":{
"Default":"Information"
}
},
"AllowedHosts":"*"
}
二、创建 Elasticsearch 服务类
创建 ElasticsearchService.cs
类,封装对 Elasticsearch 的基本操作,如写入文档和查询数据:
public classElasticsearchService
{
privatereadonly HttpClient _client;
privatereadonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
public ElasticsearchService(HttpClient client, IConfiguration config)
{
var uri = config["Elasticsearch:Url"];
var username = config["Elasticsearch:Username"];
var password = config["Elasticsearch:Password"];
client.BaseAddress = new Uri(uri);
var byteArray = Encoding.ASCII.GetBytes($"{username}:{password}");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
_client = client;
}
public async Task<bool> IndexAsync<T>(T document, string indexName, string? id = null)
{
var url = id != null ? $"/{indexName}/_doc/{id}" : $"/{indexName}/_doc";
var content = new StringContent(JsonSerializer.Serialize(document, _jsonOptions), Encoding.UTF8, "application/json");
var response = await _client.PostAsync(url, content);
return response.IsSuccessStatusCode;
}
public async Task<List<T>> SearchAsync<T>(object query, string indexName)
{
var content = new
StringContent(JsonSerializer.Serialize(query, _jsonOptions), Encoding.UTF8, "application/json");
var response = await _client.PostAsync($"/{indexName}/_search", content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
usingvar doc = JsonDocument.Parse(json);
var hits = doc.RootElement.GetProperty("hits").GetProperty("hits");
var result = new List();
foreach (var hit in hits.EnumerateArray())
{
var source = hit.GetProperty("_source").GetRawText();
var obj = JsonSerializer.Deserialize(source, _jsonOptions);
if (obj != null) result.Add(obj);
}
return result;
}
}
三、注册服务
在 Program.cs
中注册 HttpClient
和 ElasticsearchService
:
builder.Services.AddHttpClient();
四、定义日志实体类
创建一个用于记录请求日志的实体类
ApiLogEntity
:
public classApiLogEntity
{
publicstring Name { get; set; }
publicstring Ip { get; set; }
publicstring Url { get; set; }
publicstring ReqMethod { get; set; }
public DateTime OpTime { get; set; }
publicstring Account { get; set; }
publicint StatusCode { get; set; }
publicstring ResponseBody { get; set; }
}
五、编写日志记录中间件
通过自定义中间件拦截请求,并将日志写入 Elasticsearch:
public classRequestLoggingMiddleware
{
privatereadonly RequestDelegate _next;
privatereadonly ILogger _logger;
privatereadonly ElasticsearchService _esService;
public RequestLoggingMiddleware(RequestDelegate next, ILogger logger, ElasticsearchService esService)
{
_next = next;
_logger = logger;
_esService = esService;
}
public async Task Invoke(HttpContext context)
{
// 判断是否启用日志记录
if (!App.Configuration["AppLogEntity"].ToBool())
{
await _next(context);
return;
}
var request = context.Request;
if (!request.Path.Value.Contains("api"))
{
await _next(context);
return;
}
request.EnableBuffering();
var originalBodyStream = context.Response.Body;
awaitusingvar responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await _next(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseText = awaitnew StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Seek(0, SeekOrigin.Begin);
var entity = new ApiLogEntity
{
Name = Environment.MachineName,
Ip = context.Connection.RemoteIpAddress?.ToString(),
Url = request.Path + request.QueryString,
ReqMethod = request.Method,
OpTime = DateTime.Now,
Account = "wx-" + App.User.FindFirst("UserID")?.Value,
StatusCode = context.Response.StatusCode,
ResponseBody = responseText
};
await _esService.IndexAsync(entity, "jltlogs");
}
finally
{
await responseBody.CopyToAsync(originalBodyStream);
context.Response.Body = originalBodyStream;
}
}
}
注册中间件:
app.UseMiddleware();
六、测试接口
1. 写入日志文档
- • 请求路径:
POST /search/index
{
"title": "Test Document",
"description": "Elasticsearch integration with HttpClient"
}