可观测性 & 日志
ACE Framework 的可观测性能力基于 stdx.log/logger 构建,提供结构化日志、MDC 全链路透传和内置健康检查端点,天然适配 ELK、Loki、Prometheus 等主流可观测性栈。
快速上手
在任意 @Service 或 @Controller 类中使用 @Log 宏注入 Logger,Logger 名称自动取当前类名:
package example.service
import ace_framework.*
import stdx.log.logger.*
@Service
class OrderService {
@Log
var log: Logger = Logger.getLogger("placeholder") // 运行时被宏替换为类名
func createOrder(userId: String): String {
log.info("创建订单", [("userId", userId), ("action", "create")])
// 业务逻辑...
let orderId = "ORD-001"
log.info("订单创建成功", [("orderId", orderId), ("userId", userId)])
return orderId
}
}@Log 宏在编译期展开,等价于:
var log: Logger = Logger.getLogger("example.service.OrderService")日志级别 API
log.debug("调试信息", [("key", "value")])
log.info("普通信息", [("requestId", "abc123"), ("latencyMs", "42")])
log.warn("警告", [("retryCount", "3")])
log.error("错误", [("error", e.message), ("stack", e.stackTrace)])所有方法签名均为 (message: String, fields: Array<(String, String)>),键值对结构化字段在 JSON 格式下以独立字段输出,在 simple/text 格式下以 key=value 追加到行尾。
MDC — 全链路上下文透传
MDC(Mapped Diagnostic Context)允许你在请求入口设置一次上下文(如 traceId、userId),后续整条调用链的所有日志自动携带这些字段,无需手动传参。
import ace_framework.observability.*
// 在中间件或请求入口设置
@Middleware[order: 1]
class TraceMiddleware <: Middleware {
public func handle(ctx: Context, next: Next): Unit {
let traceId = ctx.header("X-Trace-Id") ?? generateTraceId()
mdc.set("traceId", traceId)
mdc.set("method", ctx.method)
mdc.set("path", ctx.path)
next()
mdc.clear() // 请求结束后清理,防止协程复用污染
}
}
// 在 Service 中,日志自动包含 traceId 字段
@Service
class UserService {
@Log var log: Logger = Logger.getLogger("placeholder")
func getUser(id: String): User {
log.info("查询用户") // 输出中自动包含 traceId、method、path
// ...
}
}协程安全
MDC 基于协程本地存储实现,每个请求协程拥有独立的上下文副本,并发请求之间互不干扰。
记得 clear()
在中间件的 next() 调用之后务必调用 mdc.clear(),否则协程池复用时上一次请求的上下文会泄漏到下一次请求。
日志格式配置
在 config/application.toml 中配置日志格式与级别:
[log]
level = "info" # debug | info | warn | error
format = "json" # json | simple | text
output = "stdout" # stdout | file
file = "logs/app.log" # 当 output = "file" 时生效JSON 格式输出示例(ELK / Loki 推荐)
{
"timestamp": "2026-07-03T10:23:45.123Z",
"level": "INFO",
"logger": "example.service.OrderService",
"message": "订单创建成功",
"traceId": "a1b2c3d4",
"orderId": "ORD-001",
"userId": "u-999"
}simple 格式输出示例
2026-07-03 10:23:45 INFO OrderService - 订单创建成功 traceId=a1b2c3d4 orderId=ORD-001健康检查
ACE Framework 内置 /health 端点,无需额外配置,启动后即可使用:
curl http://localhost:8080/health{
"status": "ok",
"uptime": 3600,
"timestamp": "2026-07-03T10:23:45.123Z"
}自定义健康指示器
实现 HealthIndicator 接口,注册为 @Service 后自动加入 /health 聚合响应:
import ace_framework.health.*
@Service
class DatabaseHealthIndicator <: HealthIndicator {
public func name(): String { "database" }
public func check(): HealthResult {
try {
db.ping()
return HealthResult.up([("pool", "10/20")])
} catch (e: Exception) {
return HealthResult.down(e.message)
}
}
}注册多个指示器后,/health 聚合输出如下:
{
"status": "ok",
"uptime": 3600,
"components": {
"database": { "status": "up", "pool": "10/20" },
"redis": { "status": "up" }
}
}| 接口方法 | 返回类型 | 说明 |
|---|---|---|
name() | String | 组件名称,作为聚合响应的 key |
check() | HealthResult | 检查逻辑,返回 up / down |
HealthResult.down(reason) 会使整体 status 变为 "degraded",HTTP 状态码返回 503。
生产环境安全
建议在生产环境通过 @Profile["prod"] + 路由鉴权保护 /health 端点,避免暴露内部组件状态。