| name | 40-log-module-architecture |
| description | Log 构建日志模块架构指南,涵盖日志接收存储、实时流式输出、多存储后端(ES/Lucene)、日志索引管理。当用户需要开发日志功能、查询构建日志、实现日志存储或处理日志流时使用。 |
Log 构建日志模块架构指南
概述
Log(构建日志)模块是 BK-CI 的日志服务,负责接收、存储和查询流水线构建过程中产生的日志数据。
模块职责:
- 构建日志的接收与存储
- 日志的实时流式输出
- 日志的查询与下载
- 多存储后端支持(ES、Lucene)
- 日志索引管理
一、模块结构
src/backend/ci/core/log/
├── api-log/ # API 接口定义
│ └── src/main/kotlin/com/tencent/devops/log/
│ ├── api/ # Resource 接口
│ │ ├── AppLogResource.kt # App 级别接口
│ │ ├── UserLogResource.kt # 用户级别接口
│ │ ├── ServiceLogResource.kt # 服务间接口
│ │ ├── OpLogResource.kt # 运营接口
│ │ └── print/ # 日志打印接口
│ │ ├── BuildLogPrintResource.kt
│ │ └── ServiceLogPrintResource.kt
│ ├── configuration/ # 配置
│ │ └── LogPrinterConfiguration.kt
│ └── meta/ # ANSI 颜色处理
│ ├── Ansi.kt
│ ├── AnsiColor.kt
│ └── AnsiAttribute.kt
├── biz-log/ # 业务逻辑层
│ └── biz-log-lucene/ # Lucene 存储实现
└── boot-log/ # 启动模块
二、日志存储架构
2.1 存储模式
┌─────────────────────────────────────────────────────────────────────────┐
│ 日志存储架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 日志接收层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Agent │ │ 插件 │ │ 其他服务 │ │ │
│ │ │ 日志上报 │ │ 日志输出 │ │ 日志调用 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 日志服务层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ │ 日志接收 │ │ 日志解析 │ │ 日志分发 │ │ │
│ │ │ LogPrint │ │ ANSI处理 │ │ 多后端写入 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 存储后端层 │ │
│ │ ┌─────────────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ ElasticSearch │ │ Lucene │ │ │
│ │ │ - 分布式存储 │ │ - 本地存储 │ │ │
│ │ │ - 全文检索 │ │ - 轻量级部署 │ │ │
│ │ └─────────────────────────┘ └─────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 存储模式选择
| 模式 | 说明 | 适用场景 |
|---|---|---|
| ElasticSearch | 分布式全文搜索引擎 | 大规模生产环境 |
| Lucene | 本地索引存储 | 轻量级部署、单机环境 |
三、数据库设计
3.1 核心表结构
| 表名 | 说明 |
|---|---|
T_LOG_INDICES_V2 |
日志索引表(关联 ES 索引) |
T_LOG_STATUS |
日志状态表(记录日志打印状态) |
T_LOG_SUBTAGS |
日志子标签表 |
3.2 关键字段说明
T_LOG_INDICES_V2:
- ID: 主键
- BUILD_ID: 构建ID
- INDEX_NAME: ES 索引名称
- LAST_LINE_NUM: 最后行号
- ENABLE: 是否启用 V2
- LOG_CLUSTER_NAME: ES 集群名称
- USE_CLUSTER: 是否使用多集群
T_LOG_STATUS:
- ID: 主键
- BUILD_ID: 构建ID
- TAG: 标签(通常是插件ID)
- SUB_TAG: 子标签
- JOB_ID: Job ID(Container Hash ID)
- USER_JOB_ID: 真正的 Job ID
- STEP_ID: 插件步骤ID
- MODE: 存储模式
- EXECUTE_COUNT: 执行次数
- FINISHED: 是否完成
四、API 接口设计
4.1 日志打印接口
@Path("/build/logs/print")
interface BuildLogPrintResource {
@POST
@Path("/")
fun addLogLine(
@HeaderParam(AUTH_HEADER_BUILD_ID) buildId: String,
@HeaderParam(AUTH_HEADER_PIPELINE_ID) pipelineId: String,
@HeaderParam(AUTH_HEADER_PROJECT_ID) projectId: String,
logMessage: LogMessage
): Result<Boolean>
@POST
@Path("/multi")
fun addLogMultiLine(
@HeaderParam(AUTH_HEADER_BUILD_ID) buildId: String,
@HeaderParam(AUTH_HEADER_PIPELINE_ID) pipelineId: String,
@HeaderParam(AUTH_HEADER_PROJECT_ID) projectId: String,
logMessages: List<LogMessage>
): Result<Boolean>
@POST
@Path("/status")
fun updateLogStatus(
@HeaderParam(AUTH_HEADER_BUILD_ID) buildId: String,
@HeaderParam(AUTH_HEADER_PIPELINE_ID) pipelineId: String,
@HeaderParam(AUTH_HEADER_PROJECT_ID) projectId: String,
logStatusEvent: LogStatusEvent
): Result<Boolean>
}
// 日志消息
data class LogMessage(
val message: String, // 日志内容
val timestamp: Long, // 时间戳
val tag: String?, // 标签
val subTag: String?, // 子标签
val jobId: String?, // Job ID
val logType: LogType?, // 日志类型
val executeCount: Int? // 执行次数
)
4.2 日志查询接口
@Path("/user/logs")
interface UserLogResource {
@GET
@Path("/{projectId}/{pipelineId}/{buildId}/")
fun getInitLogs(
@PathParam("projectId") projectId: String,
@PathParam("pipelineId") pipelineId: String,
@PathParam("buildId") buildId: String,
@QueryParam("tag") tag: String?,
@QueryParam("subTag") subTag: String?,
@QueryParam("jobId") jobId: String?,
@QueryParam("executeCount") executeCount: Int?
): Result<QueryLogs>
@GET
@Path("/{projectId}/{pipelineId}/{buildId}/more")
fun getMoreLogs(
@PathParam("projectId") projectId: String,
@PathParam("pipelineId") pipelineId: String,
@PathParam("buildId") buildId: String,
@QueryParam("num") num: Int?,
@QueryParam("fromStart") fromStart: Boolean?,
@QueryParam("start") start: Long,
@QueryParam("end") end: Long,
@QueryParam("tag") tag: String?,
@QueryParam("subTag") subTag: String?,
@QueryParam("jobId") jobId: String?,
@QueryParam("executeCount") executeCount: Int?
): Result<QueryLogs>
@GET
@Path("/{projectId}/{pipelineId}/{buildId}/download")
fun downloadLogs(
@PathParam("projectId") projectId: String,
@PathParam("pipelineId") pipelineId: String,
@PathParam("buildId") buildId: String,
@QueryParam("tag") tag: String?,
@QueryParam("subTag") subTag: String?,
@QueryParam("jobId") jobId: String?,
@QueryParam("executeCount") executeCount: Int?
): Response
}
4.3 服务间接口
@Path("/service/logs")
interface ServiceLogResource {
@POST
@Path("/{projectId}/{pipelineId}/{buildId}/")
fun addLogLine(
@PathParam("projectId") projectId: String,
@PathParam("pipelineId") pipelineId: String,
@PathParam("buildId") buildId: String,
logMessage: LogMessage
): Result<Boolean>
@GET
@Path("/{projectId}/{pipelineId}/{buildId}/status")
fun getLogStatus(
@PathParam("projectId") projectId: String,
@PathParam("pipelineId") pipelineId: String,
@PathParam("buildId") buildId: String,
@QueryParam("tag") tag: String?,
@QueryParam("executeCount") executeCount: Int?
): Result<LogStatus>
}
五、日志处理流程
5.1 日志写入流程
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Agent/ │ │ Log │ │ Log │ │ ES/ │
│ Plugin │ │ Print │ │ Service │ │ Lucene │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
│ 1.上报日志 │ │ │
│──────────────>│ │ │
│ │ │ │
│ │ 2.解析ANSI │ │
│ │───────┐ │ │
│ │ │ │ │
│ │<──────┘ │ │
│ │ │ │
│ │ 3.批量写入 │ │
│ │──────────────>│ │
│ │ │ │
│ │ │ 4.索引存储 │
│ │ │──────────────>│
│ │ │ │
│ 5.确认 │ │ │
│<──────────────│ │ │
5.2 日志查询流程
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 前端 │ │ Log │ │ Log │ │ ES/ │
│ │ │ Resource│ │ Service │ │ Lucene │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
│ 1.查询日志 │ │ │
│──────────────>│ │ │
│ │ │ │
│ │ 2.获取索引 │ │
│ │──────────────>│ │
│ │ │ │
│ │ │ 3.查询存储 │
│ │ │──────────────>│
│ │ │ │
│ │ │ 4.返回结果 │
│ │ │<──────────────│
│ │ │ │
│ │ 5.格式化日志 │ │
│ │<──────────────│ │
│ │ │ │
│ 6.返回日志 │ │ │
│<──────────────│ │ │
六、ANSI 颜色支持
6.1 颜色代码
enum class AnsiColor(val code: Int) {
BLACK(30),
RED(31),
GREEN(32),
YELLOW(33),
BLUE(34),
MAGENTA(35),
CYAN(36),
WHITE(37),
DEFAULT(39)
}
enum class AnsiAttribute(val code: Int) {
RESET(0),
BOLD(1),
ITALIC(3),
UNDERLINE(4)
}
6.2 日志着色示例
// 在 Agent/插件中输出彩色日志
println("\u001B[32m[SUCCESS]\u001B[0m Build completed") // 绿色
println("\u001B[31m[ERROR]\u001B[0m Build failed") // 红色
println("\u001B[33m[WARNING]\u001B[0m Low disk space") // 黄色
七、与其他模块的关系
7.1 依赖关系
Log 模块
│
├──< Process(流水线)
│ - 构建过程日志输出
│ - 日志状态管理
│
├──< Worker(构建机)
│ - Agent 日志上报
│ - 插件日志输出
│
├──< Dispatch(调度)
│ - 调度日志记录
│
└──> ElasticSearch / Lucene
- 日志存储后端
八、开发规范
8.1 日志打印示例
// 在插件中打印日志
class MyPlugin : Plugin {
override fun execute(context: PluginContext) {
// 普通日志
context.logger.info("开始执行插件")
// 带颜色的日志
context.logger.info("\u001B[32m[SUCCESS]\u001B[0m 步骤完成")
// 错误日志
context.logger.error("执行失败: ${e.message}")
}
}
// 通过 API 打印日志
client.get(ServiceLogPrintResource::class).addLogLine(
projectId = projectId,
pipelineId = pipelineId,
buildId = buildId,
logMessage = LogMessage(
message = "自定义日志内容",
timestamp = System.currentTimeMillis(),
tag = elementId,
logType = LogType.LOG
)
)
8.2 日志查询示例
// 获取构建日志
val logs = client.get(ServiceLogResource::class).getInitLogs(
projectId = projectId,
pipelineId = pipelineId,
buildId = buildId,
tag = elementId,
executeCount = 1
)
// 获取更多日志(分页)
val moreLogs = client.get(ServiceLogResource::class).getMoreLogs(
projectId = projectId,
pipelineId = pipelineId,
buildId = buildId,
start = 100,
end = 200,
tag = elementId
)
8.3 日志标签规范
| 标签 | 说明 |
|---|---|
tag |
通常是插件/Element ID |
subTag |
子任务标识 |
jobId |
Container Hash ID |
userJobId |
用户定义的 Job ID |
stepId |
用户定义的步骤 ID |
九、常见问题
Q: 日志存储在哪里? A: 根据配置,可以存储在 ElasticSearch(生产环境推荐)或 Lucene(轻量级部署)。
Q: 日志保留多久? A: 通过 ES 索引策略或配置文件设置,通常保留 30-90 天。
Q: 如何查看特定插件的日志?
A: 使用 tag 参数过滤,tag 通常对应插件的 Element ID。
Q: 日志太大怎么办? A:
- 使用分页查询(
start/end参数) - 下载日志文件
- 按
tag/subTag过滤
版本: 1.0.0 | 更新日期: 2025-12-11