# LogCenterService
**Repository Path**: yokogood/LogCenterService
## Basic Information
- **Project Name**: LogCenterService
- **Description**: 日志中心服务
- **Primary Language**: C#
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2024-06-18
- **Last Updated**: 2024-06-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[TOC]
# ✨ 1、日志格式
## 1.1 日志示例
✔️ 集合压缩
```json
[{"TimeStamp":"2023-11-10 14:24:17.134","Level":"Warning","Message":"\"cluster1\" 集群可以工作。140086652588549","TrackingId":"140086652588549","Path":"","Source":"ScreenControlService","Version":"1.0.0"},{"TimeStamp":"2023-11-10 14:24:17.145","Level":"Warning","Message":"\"cluster1\" 已唤醒。140086652588549 ","TrackingId":"140086652588549","Path":"","Source":"ScreenControlService","Version":"1.0.0"},{"TimeStamp":"2023-11-10 14:27:51.853","Level":"Information","Message":"获取当前在线的所有设备:\"\"","TrackingId":"","Path":"/api/Nova/GetAllIds","Source":"ScreenControlService","Version":"1.0.0"},{"TimeStamp":"2023-11-10 14:27:51.864","Level":"Information","Message":"HTTP \"GET\" \"/api/Nova/GetAllIds\" responded 200 in 195.7098 ms","TrackingId":"","Path":"/api/Nova/GetAllIds","Source":"ScreenControlService","Version":"1.0.0"}]
```
✔️ 格式化
```json
[
{
"TimeStamp": "2023-11-10 14:24:17.134",
"Level": "Warning",
"Message": "\"cluster1\" 集群可以工作。140086652588549",
"TrackingId": "140086652588549",
"Path": "",
"Source": "ScreenControlService",
"Version": "1.0.0"
},
{
"TimeStamp": "2023-11-10 14:24:17.145",
"Level": "Warning",
"Message": "\"cluster1\" 已唤醒。140086652588549 ",
"TrackingId": "140086652588549",
"Path": "",
"Source": "ScreenControlService",
"Version": "1.0.0"
},
{
"TimeStamp": "2023-11-10 14:27:51.853",
"Level": "Information",
"Message": "获取当前在线的所有设备:\"\"",
"TrackingId": "",
"Path": "/api/Nova/GetAllIds",
"Source": "ScreenControlService",
"Version": "1.0.0"
},
{
"TimeStamp": "2023-11-10 14:27:51.864",
"Level": "Information",
"Message": "HTTP \"GET\" \"/api/Nova/GetAllIds\" responded 200 in 195.7098 ms",
"TrackingId": "",
"Path": "/api/Nova/GetAllIds",
"Source": "ScreenControlService",
"Version": "1.0.0"
}
]
```
✔️ 单个对象
```json
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": "测试测试测试测试测试测试测试测试测试测试测试测试",
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
}
```
✔️ 消息体包含对象
```json
[
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": [
{
"platenumber": "苏B66666",
"startparkingtime": "2024-01-09 14:15:48",
"endparkingtime": "2024-01-10 10:21:25",
"totleduration": 1206,
"actualduration": 1176,
"actualprice": 30.0,
"paiedprice": 0.0,
"isfreeplatenumber": "0",
"finestatus": 1,
"sectionname": "测试路段",
"paymentinfos": []
}
],
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
},
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": "测试测试测试测试测试测试测试测试测试",
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
}
]
```
## 1.2 说明
### 1.2.1 参数
| 参数 | 说明 |
| ---------- | ------------------------------------------------------------ |
| TimeStamp | 日志发生时间(yyyy-MM-dd HH:mm:ss.fff) |
| Level | 日志事件的级别:Verbose, Debug, Information, Warning, Error, Fatal |
| Message | 日志事件的消息文本 |
| TrackingId | 追踪Id |
| Path | 请求路径 |
| Source | 程序的来源标识/应用名称 |
| Version | 版本号 |
### 1.2.2 日志等级
约定日志等级如下:
| 参数 | 说明 |
| ----------- | ------------ |
| Verbose | 最复杂的级别 |
| Debug | 调试级别 |
| Information | 信息级别 |
| Warning | 警告级别 |
| Error | 错误级别 |
| Fatal | 致命错误 |
### 1.2.3 追踪Id
> 前端部分
如公众号,需要按规范在HTTP头部加上 “traceparent” 属性,并按W3C Trace Context 规范,设置 “traceparent” 的值。
HTTP头部 “traceparent” 的值应遵循 `00-{traceId}-{spanId}-{traceFlags}` 的格式
其中:
- `traceId` 是一个 32 个十六进制数字的字符串,表示追踪操作的唯一标识符。
- `spanId` 是一个 16 个十六进制数字的字符串,表示单个操作的唯一标识符 (例如,一个 HTTP 请求)。
- `traceFlags` 是一个 2 个十六进制数字的字符串,表示追踪选项。目前,唯一定义的选项是 “01”,表示记录追踪数据
示例值:
```tex
00-83efd447300bf4dfa660e807869e78f0-3f977cb3083d2ac6-01
```
# ✨ 2、 API接口
## 🍎 2.1 健康检查
### 简要描述
- 日志服务的健康检查接口
### 请求URL
- ` http://198.98.98.153:6162/ok
### 请求方式
- GET
### 返回示例
``` json
日志服务运行正常!
```
## 🍎 2.2 上传日志
### 简要描述
- 用于接收来自 `Serilog.Sinks.Http` 的日志事件。
### 请求URL
- ` http://198.98.98.153:6162/api/log/insert `
### 请求方式
- POST
### 请求参数示例
Body:
```json
[
{
"TimeStamp": "2023-11-09 15:11:38.123",
"Level": "Warning",
"Message": "当前作业调度器启动139744412082693",
"TrackingId": "",
"Path": "",
"Source": "ScreenControlService-1.0.0"
},
{
"TimeStamp": "2023-11-09 15:11:38.123",
"Level": "Warning",
"Message": "139744412082693等待被唤醒",
"TrackingId": "",
"Path": "",
"Source": "ScreenControlService-1.0.0"
},
{
"TimeStamp": "2023-11-09 15:11:38.123",
"Level": "Information",
"Message": "客户端 \"198.98.98.248:46032\" 上线",
"TrackingId": "",
"Path": "",
"Source": "ScreenControlService-1.0.0"
},
{
"TimeStamp": "2023-11-09 15:11:39.123",
"Level": "Information",
"Message": "设备验证成功,允许入网。收到(数据域):\"31-39-38-30-30-39-38-30-30-39-38-30-32-35-31\"",
"TrackingId": "198009800980251",
"Path": "198.98.98.248:46032",
"Source": "ScreenControlService-1.0.0"
},
{
"TimeStamp": "2023-11-09 15:11:39.123",
"Level": "Information",
"Message": "回复\"198009800980251\":\"AAFFFF002B864C65CC9B10\"",
"TrackingId": "198009800980251",
"Path": "198.98.98.248:46032",
"Source": "ScreenControlService-1.0.0"
}
]
```
### 返回示例
- 无返回数据,如果请求成功,服务器将返回 HTTP 200 OK 响应。
# ✨ 3、 后端接入日志服务示例
## 3.1、追踪标识说明
> 前端部分
如公众号,需要按规范在HTTP头部加上 “traceparent” 属性,并按W3C Trace Context 规范,设置 “traceparent” 的值。
HTTP头部 “traceparent” 的值应遵循 `00-{traceId}-{spanId}-{traceFlags}` 的格式
其中:
- `traceId` 是一个 32 个十六进制数字的字符串,表示追踪操作的唯一标识符。
- `spanId` 是一个 16 个十六进制数字的字符串,表示单个操作的唯一标识符 (例如,一个 HTTP 请求)。
- `traceFlags` 是一个 2 个十六进制数字的字符串,表示追踪选项。目前,唯一定义的选项是 “01”,表示记录追踪数据
示例值:
```tex
00-83efd447300bf4dfa660e807869e78f0-3f977cb3083d2ac6-01
```
## 3.2、追踪上下文
> 后端服务部分,插件式快速接入
---
部分插件:
> 注意:只支持Net6、Net7、Net8
- Yoko.Tool.Core [](https://www.nuget.org/packages/Yoko.Tool.Core/)
- Serilog.Sinks.TraceContext [](https://www.nuget.org/packages/Serilog.Sinks.TraceContext/)

---
### 1、 将追踪上下文注入到 HTTP 头部
```c#
// 将追踪上下文注入到 HTTP 头部
builder.Services.AddTracingDelegatingHandler();
```
这个方法是用于在分布式服务中注入追踪上下文至 HTTP 头部。
它遵循 W3C Trace Context 规范,通过创建一个 `TracingDelegatingHandler` 实例,并将其添加到 HttpClient 的消息处理程序中。这样,每当 HttpClient 发送请求时,都会自动将追踪上下文注入到 HTTP 请求头部。
### 2、设置跟踪上下文中间件
检查上游HTTP追踪上下文,将其设置为当前活动
```c#
//设置跟踪上下文中间件
app.UseSetTraceContextMiddleware();
```
这个方法是用于设置跟踪上下文中间件,它会检查上游 HTTP 追踪上下文,并将其设置为当前活动。
这个中间件首先从 HTTP 请求头部提取追踪上下文,然后创建一个新的 Activity,并将其设置为当前活动。
这样,你就可以在整个请求处理过程中访问和操作这个 Activity。
### 3、使用HttpClient
**需要注意的是,在使用HttpClient的时候,要注册名为 “TracingClient” 的 HttpClient**
示例:
```c#
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using Microsoft.Extensions.Logging;
namespace YourNamespace.Controllers
{
[ApiController]
[Route("[controller]")]
public class YourController : ControllerBase
{
private readonly ILogger _logger;
private readonly HttpClient _client;
public YourController(ILogger logger, IHttpClientFactory clientFactory)
{
_logger = logger;
// 注意这里
_client = clientFactory.CreateClient("TracingClient");
}
[HttpGet("get")]
public async Task GetAsync()
{
var response = await _client.GetAsync("http://example.com/api/values");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return Ok(content);
}
else
{
return StatusCode((int)response.StatusCode, response.ReasonPhrase);
}
}
[HttpPost("post")]
public async Task PostAsync([FromBody] YourModel model)
{
var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
var response = await _client.PostAsync("http://example.com/api/values", content);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
return Ok(responseContent);
}
else
{
return StatusCode((int)response.StatusCode, response.ReasonPhrase);
}
}
}
}
```
## 3.3、设置 Serilog 提取追踪上下文
### 1、提取追踪上下文
首先,你需要在你的 Serilog 配置中使用 `WithTraceContextHeader` 方法:
```c#
Log.Logger = new LoggerConfiguration()
.Enrich.WithTraceContextHeader()
// 其他配置...
.CreateLogger();
```
默认情况下,这个插件会从 “traceparent” 头部提取追踪上下文。
然后,你就可以像平常一样使用 Serilog 来记录日志。
每个日志事件都会包含两个额外的属性:`TraceId` 和 `SpanId`。
这些属性的值来自 “traceparent” 头部。
### 2、日志格式化器
Serilog.Sinks.TraceContext插件还包含一个自定义的日志格式化器,它可以输出两种不同的日志格式:JSON格式和文件格式。
首先, `LogFormatter` 实例你可以选择是否使用文件日志格式:
```c#
var formatter = new LogFormatter(isFileLog: true); // 使用文件日志格式
```
或者:
```c#
var formatter = new LogFormatter(); // 使用JSON日志格式
```
然后,你可以在你的日志配置中使用这个格式化器:
```c#
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.MinimumLevel.Debug()
.Enrich.FromLogContext()
//================== 上传日志依赖插件:Serilog.Sinks.Http =====================
.WriteTo.Http(requestUri: $"{AppSettings.LogServiceCenterUrl}/api/log/insert"
, queueLimitBytes: null
, textFormatter: new LogFormatter() // 使用JSON格式化器 <========= 【上传日志到服务中心,必须用这个格式化器】
)
.WriteTo.File(
formatter: new LogFormatter(true), // 使用文件格式化器 <========= 看这里===========
path: $"{AppDomain.CurrentDomain.BaseDirectory}/Logs/Service-.log",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 10485760,
encoding: System.Text.Encoding.UTF8,
retainedFileCountLimit: 200,
shared: true
)
.CreateLogger();
```
#### 格式说明
##### 🔎 JSON日志格式
上传到日志服务的日志格式。
以下是一个JSON日志格式的示例:
```json
{
"TimeStamp": "2024-04-29 10:35:34.576",
"Level": "Information",
"Message": "服务启动成功",
"TrackingId": "1234567890",
"Path": "/api/start",
"Source": "MyApp",
"Version": "1.0.0"
}
```
##### 🔎 文件日志格式
本地保存的日志格式。
以下是一个文件日志格式的示例:
```tex
[2024-04-29 10:35:34.576] [Information] [83efd447300bf4dfa660e807869e78f0] [/api/start] 服务启动成功
```
在这个示例中,每个方括号内的字段分别代表时间戳、日志级别、跟踪ID和请求路径,最后是日志消息本身。
## 3.4、完整演示示例
Program.cs
```c#
try
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// 将追踪上下文注入到 HTTP 头部
builder.Services.AddTracingDelegatingHandler();
// Serilog
builder.Host.UseSerilog();
// 注册Http上下文访问器
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
//设置HTTP跟踪上下文中间件
app.UseSetTraceContextMiddleware();
#region 配置Serilog
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Default", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithTraceContextHeader()
.WriteTo.Http(requestUri: $"{AppSettings.LogServiceCenterUrl}/api/log/insert" //接口地址
, queueLimitBytes: null
, textFormatter: new LogFormatter()//使用JSON格式化器
)
.WriteTo.File(
formatter: new LogFormatter(true), // 使用文件格式化器
path: $"{AppDomain.CurrentDomain.BaseDirectory}/Logs/Service-.log",
rollingInterval: RollingInterval.Day,//日志按天保存
rollOnFileSizeLimit: true,//限制单个文件的最大长度
fileSizeLimitBytes: 10485760,//单个文件最大10M
encoding: System.Text.Encoding.UTF8,//文件字符编码
retainedFileCountLimit: 200,//将被保留的日志文件的最大数量,包括当前日志文件。对于无限保留,传递null。默认值为31。
shared: true//允许多个进程共享日志文件。默认值为false
)
.CreateLogger(); //立即配置 Serilog
#endregion
app.UseAuthorization();
app.MapControllers();
Log.Information("服务启动成功");
app.MapGet("/ok", () => "服务运行正常!").WithTags("健康检查");
app.Run();
}
catch (Exception e)
{
Log.Fatal("应用程序意外终止!{msg}", e.Message);
}
finally
{
Log.CloseAndFlush();
}
```
# ✨ 4、补充说明
## Serilog.Sinks.Http 参数
### 🌴 Http
添加一个 `非持久性` 接收器,使用HTTP POST通过网络发送日志事件。如果无法访问日志服务器,日志事件将存储在内存中。
存储在内存中的日志事件的最大数量是可配置的,如果达到此限制,接收器将丢弃新的日志事件以保留旧的。
非持久性接收器在系统或进程重启后会丢失数据。
🍀 主要参数
- sinkConfiguration:记录器配置。
- requestUri:发送请求的URI
- queueLimitBytes :存储在内存中等待通过网络发送的事件的最大大小(以字节为单位)。指定null表示没有限制。
### 🌴 DurableHttpUsingFileSizeRolledBuffers
添加一个 `持久性 ` 接收器,使用HTTP POST通过网络发送日志事件。
如果无法访问日志服务器,日志事件将始终存储在磁盘上。
缓冲文件将使用中指定的文件大小定义的滚动行为,即当当前缓冲文件达到其限制时,将创建一个新的缓冲文件。
保留的文件数量最大值由定义,当达到该限制时,最旧的文件将被删除以腾出空间。持久性接收器将在系统或进程重启后保护您免受数据丢失。
🍀 主要参数
- sinkConfiguration:记录器配置
- requestUri:发送请求的URI
- bufferBaseFileName:一组文件的相对或绝对路径,这些文件将用于缓冲事件,直到它们能够成功通过网络传输。将使用模式“-*.txt”创建单个文件,该模式不应与同一目录中的任何其他文件名冲突。默认值为“Buffer”。
- bufferFileSizeLimitBytes:缓冲文件允许增长到的近似最大大小(以字节为单位)。对于无限制增长,请传递null。默认值为1 GB。为避免写入部分事件,即使超过限制,也将完整写入限制内的最后一个事件。
- bufferFileShared:允许多个进程共享缓冲文件。默认值为false。
- retainedBufferFileCountLimit:将保留的缓冲文件的最大数量,包括当前缓冲文件。在正常操作下,只会保留2个文件,但是如果无法访问日志服务器,则将在文件系统上保留指定的文件数量。对于无限制保留,请传递null。默认值为31。
### 🌴 DurableHttpUsingTimeRolledBuffers
添加一个 `持久性` 接收器,使用HTTP POST通过网络发送日志事件
如果无法访问日志服务器,日志事件将始终存储在磁盘上。
缓冲文件将使用由指定的时间间隔定义的滚动行为, 即每当开始一个新的间隔时,都会创建一个新的缓冲文件。
缓冲文件的最大大小由定义,当达到该限制时,所有新的日志事件都将被丢弃,直到开始一个新的间隔。
持久性接收器将在系统或进程重启后保护您免受数据丢失。
🍀 主要参数
- sinkConfiguration:记录器配置
- requestUri:发送请求的URI
- bufferBaseFileName:一组文件的相对或绝对路径,这些文件将用于缓冲事件,直到它们能够成功通过网络传输。将使用模式“-*.txt”创建单个文件,该模式不应与同一目录中的任何其他文件名冲突。默认值为“Buffer”。
- bufferRollingInterval:旋转缓冲文件的间隔。默认值为Day。
- bufferFileSizeLimitBytes:特定时间间隔的缓冲文件允许增长到的近似最大大小(以字节为单位)。
默认情况下不会应用任何限制。
- bufferFileShared:允许多个进程共享缓冲文件。默认值为false。
- retainedBufferFileCountLimi:将保留的缓冲文件的最大数量,包括当前缓冲文件。
在正常操作下只会保留2个文件,但是如果日志服务器无法访问,则将保留由指定数量的文件在文件系统上。
对于无限保留,传递null。默认值为31。
### 🍀 共有参数
- logEventLimitBytes:日志事件序列化表示形式的最大大小(以字节为单位)。超过此大小的日志事件将被丢弃。
指定null表示没有限制。默认值为null。
- logEventsInBatchLimit:作为单个批次通过网络发送的日志事件的最大数量。默认值为1000。
- batchSizeLimitBytes:单个批次的近似最大大小(以字节为单位)。该值是近似值,因为只考虑了日志事件的大小。批处理格式化程序添加的额外字符,其中序列化日志事件序列转换为有效载荷,不予考虑。请确保适应这些字符。
另一件需要提及的事情是,尽管接收器尽力优化此限制,但如果您决定使用压缩有效载荷的实现,例如,则此参数描述了日志事件的未压缩大小。压缩后的大小可能会根据压缩算法和日志事件的重复性显著减小。
默认值为null。
- period:检查事件批次之间等待的时间。默认值为2秒。
- textFormatter:将单个日志事件渲染为文本的格式化程序,例如JSON。默认值为。
- batchFormatter:将多个日志事件批量处理成可以通过网络发送的有效载荷的格式化程序。默认值为。
- restrictedToMinimumLevel:通过接收器传递的事件的最低级别。当指定时忽略。默认值为。
- levelSwitch:允许在运行时更改通过最小级别的开关。
- httpClient:自定义实现。默认值为。
- configuration:传递给的配置。参数可以在源代码中配置接收器时手动指定,也可以在使用[Serilog.Settings.Configuration](https://www.nuget.org/packages/Serilog.Settings.Configuration)配置接收器时自动传入。
# 更新日志
`2.0.0`
- 基于端点重构项目
`1.2.1`
【新增】
- 单个对象的支持
```json
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": "测试测试测试测试测试测试测试测试测试测试测试测试",
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
}
```
- 消息体包含对象的支持
```json
[
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": [
{
"platenumber": "苏B66666",
"startparkingtime": "2024-01-09 14:15:48",
"endparkingtime": "2024-01-10 10:21:25",
"totleduration": 1206,
"actualduration": 1176,
"actualprice": 30.0,
"paiedprice": 0.0,
"isfreeplatenumber": "0",
"finestatus": 1,
"sectionname": "测试路段",
"paymentinfos": []
}
],
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
},
{
"TimeStamp": "2024-01-10 17:46:23.552",
"Level": "Information",
"Message": "测试测试测试测试测试测试测试测试测试",
"TrackingId": "3e56259f-188d-4eba-9729-ca88b6b36000",
"Path": "",
"Source": "test",
"Version": "1.0"
}
]
```
# QuestDB 安装
## 一、下载
官方下载地址:
- https://questdb.io/get-questdb/
- https://questdb.io/download/
- https://github.com/questdb/questdb/releases
## 二、环境准备
#### 1、Java 11
需要在本地安装 Java 11
```sh
#检查版本
java -version
```
支持
- [AdoptOpenJDK](https://adoptium.net/zh-CN/temurin/releases/?version=11)
- Amazon Corretto
- [OpenJDK](https://openjdk.org/) `推荐`
- [Oracle Java](https://www.oracle.com/cn/java/technologies/downloads/#java11-windows)
#### 2、JAVA_HOME
需要将环境变量`JAVA_HOME`设置为 JDK 的安装文件夹。

#### 3、提取压缩包
```sh
# tar -xvf 包名
tar -xvf questdb-6.5.5-rt-windows-amd64.tar.gz
```
## 三、启动
根目录新建 run.bat
```sh
start bin\questdb.exe
```
其他命令:start|stop|status|install|remove
```sh
# 安装
questdb.exe install
# 启动
questdb.exe start
# 停止
questdb.exe stop
# 其他
```
## 四、使用
默认启动端口:
- 9000 :Web控制台
- 9009:influxDB 线路协议
- 8812:Postgres 有线协议
> 数据库固定连接字符串:host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;
- 9003:最小健康服务器
注:相关端口可以修改 QuestDB\7.3.10\qdbroot\conf\server.conf