Skip to content

可观测性 & 日志

ACE Framework 的可观测性能力基于 stdx.log/logger 构建,提供结构化日志、MDC 全链路透传和内置健康检查端点,天然适配 ELK、Loki、Prometheus 等主流可观测性栈。

快速上手

在任意 @Service@Controller 类中使用 @Log 宏注入 Logger,Logger 名称自动取当前类名:

cangjie
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 宏在编译期展开,等价于:

cangjie
var log: Logger = Logger.getLogger("example.service.OrderService")

日志级别 API

cangjie
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),后续整条调用链的所有日志自动携带这些字段,无需手动传参。

cangjie
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 中配置日志格式与级别:

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 推荐)

json
{
  "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 端点,无需额外配置,启动后即可使用:

bash
curl http://localhost:8080/health
json
{
  "status": "ok",
  "uptime": 3600,
  "timestamp": "2026-07-03T10:23:45.123Z"
}

自定义健康指示器

实现 HealthIndicator 接口,注册为 @Service 后自动加入 /health 聚合响应:

cangjie
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 聚合输出如下:

json
{
  "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 端点,避免暴露内部组件状态。

基于 Apache-2.0 许可证发布