Skip to content

日志、指标与追踪

方块的日志汇成一条流(实例的输出、每个请求一行、平台做了什么),还有它的指标、平台为每个请求记录的 span(边缘节点的和机器的),以及把它们导出到任意 OpenTelemetry 接收端。

更新于

应用的日志

一个应用(即一个方块)只有一份日志,由为它提供服务的每台机器的日志汇集而成,按时间排序。日志行有三种:

  • output:实例的启动命令在 stdout 和 stderr 上打印的内容;
  • request:每个请求一行:方法、路径、状态、耗时、字节数、请求的 id;对于唤醒了实例的请求,还有实例之前是如何停止的,以及唤醒用了多久;
  • platform:平台做了什么:构建了模板、创建或停止了实例、某个版本开始提供服务。
glac cube logs api -f                        # follow
glac cube logs api --since 2h --status 5xx   # requests that failed in the last two hours

GET /v1/apps/{app}/logs 读取日志:可以从某个时间读(since、until),从读取方上次停下的地方读 (cursor),或持续跟随新行(follow=true;账户所有项目合计、同时能跟随应用日志的读取方数量,见下表)。可以按实例、版本和类型筛选,也可以按日志行的内容筛选:status(500、5xx、>=400)、method、path、request_id、event、stream、contains。日志行以每行一个 JSON 对象的形式返回,或以 server-sent events 返回。

永久丢失的日志行(机器在转发之前就没了、缓冲区已满)会在原处留下一行 logs.gap,说明是哪段时间、丢了多少行。沙箱自己的命令不在任何应用的日志中:它们的输出随命令读取 (GET /v1/sandboxes/{id}/exec/{command}/output)。有自己存储卷的服务也一样,它的启动命令是 exe-start。

保留什么,保留多久

免费版 付费版
方块日志行的保留天数 3 天 30 天
日志导出目标 1 10
同时实时跟踪账户内应用日志的连接数 10 100

应用的 logs 设置立即生效:output: false 不再读取实例打印的内容,keep: false 不把日志行放进长期存储,requests.sample_rate 只保留一部分请求日志行(设置了 requests.keep_errors 时,失败的请求总会保留)。GET /v1/account 会说明日志受到的限制 (logs):最长的行、实例每秒可写入的行数,以及已删除实例的日志保留多少。

glac cube log-usage api (GET /v1/apps/{app}/logs/usage) 按天统计打印了多少、保留了多少、丢弃了多少以及原因。glac cube log-delete api --before 2d (DELETE /v1/apps/{app}/logs) 删除某个时间之前的日志行。

谁会读取它。你项目的密钥,以及我们的运营人员在排查问题时。我们的运营人员每读取一次应用的日志,你项目的审计日志 (GET /v1/audit) 中就会有一条记录,而且在运营人员看到任何一行之前就已写入:actor.type 为 operator,log_read 说明是哪个应用、读取的时间段(since、until),以及运营人员是否跟随了新行。平台自己用于存储日志、把日志发往你的导出目标的程序不算读取者,不会留下记录。

费用。实例打印并被保留的内容,按打印时的大小计(而不是压缩后存储的大小),保留多久就计多久:超出付费版每月包含的 10 GB·月 后,每 GB·月 $0.02(参见价格与套餐)。所以一个月里打印 1 GB、保留 7 天,大约是四分之一 GB·月。免费版不收费:每天保留 200 MB 的打印内容。请求日志行、平台日志行和不保留的输出都不计费。套餐的日志额度用完且余额为零时,应用此后打印的内容不再长期保留:在机器上仍可读取一段时间,日志会用一行 logs.keep_off (reason: over_plan) 说明这一点,恢复保留时再出现一行 logs.keep_on。每台机器得知后就停止保留,通常在一秒之内(无法访问的机器,则在恢复访问时):在此之前打印的内容照常保留和计费,余额付不起的部分予以核销,不算欠款(对账单中的 written_off_usd)。

现在保留在哪里。应用的 logs_state 为每台保存该应用日志的机器各列一项,内容是机器最近一次的报告(每隔几秒一次):for_good,表示那里现在打印的内容是否长期保留(送到对象存储、按套餐的天数保留并计费);不长期保留时有 reason:over_plan,或 setting(应用的 logs.keep 为 false);以及 since,按机器的时钟。

指标

GET /v1/apps/{app}/metrics 给出最近 24 小时每分钟、或 30 天内每小时的数据:按状态码类别统计的请求数、耗时的 p50 和 p99、运行中和已停止的实例数、正在处理的请求数、唤醒次数、新建的实例数、CPU 和内存。

glac cube metrics api --from 6h            # a row a minute; --by-hour: a row an hour

SDK 中是 app.metrics(),MCP 服务器中是 cube_metrics。

追踪

请求的 traceparent 和 tracestate (W3C Trace Context) 会按调用方发送的原样,经过边缘节点和机器到达你的实例,HTTP/1.1 和 HTTP/2 都一样。所以你的应用自己的 OpenTelemetry SDK 无需任何设置就能延续调用方的追踪。

应用的 traces 设置让平台参与它的请求的追踪:

PATCH /v1/apps/api
{"traces": {"sample_rate": 0.1, "trust_incoming": false}}
  • sample_rate(0 到 1,默认 0)是平台为之创建 span 的请求比例。接收请求的机器会创建一个 span:从读取请求头开始,到响应的最后一个字节为止,以方法命名(GET;HTTP 中没有的方法命名为 HTTP,http.request.method 为 _OTHER,实际方法放在 http.request.method_original 中),带有 http.request.method、url.path、url.scheme、server.address、http.response.status_code、http.response.size 和 sbx.request.id,并把实例的唤醒(sbx.instance.woken,带 sbx.wake_ms)和为该请求新建实例 (sbx.instance.made) 记为事件。5xx、没有响应,或平台自己以 5xx 拒绝,都算错误;4xx 不算。为 0 时,平台不参与,请求头原样传递。
  • 大于 0 时,无论是否采样,每个请求的实例收到的 traceparent 都以平台的 span 为父级,取代调用方的。你的 SDK 创建的 span 会成为平台 span 的子 span,使用默认采样器(基于父级)的 SDK 恰好会采样平台采样的那些请求。
  • trust_incoming(默认 false)决定是否采用请求自带的 traceparent。为 true 时:平台的 span 加入调用方的追踪,第一个 span 是调用方 span 的子 span,调用方说采样就采样(不论比例),调用方的 tracestate 继续传递。为 false 时:每个请求开始一条自己的追踪,按比例采样,它的第一个 span 链接到调用方的 span;调用方的 tracestate 不会到达实例。受信任的调用方能决定采样和发送什么,所以对任何人都能访问的应用,请保持 false。
  • 日志中这个请求的那一行会注明追踪 (trace_id),请求被采样时还会注明平台的 span (span_id),这样日志行和 span 就能互相对应;导出时,它们是记录的 TraceId 和 SpanId。
  • span 会发往项目中接收 traces 的导出目标(见下文),从机器拿到这个目标起开始发送(目标创建后片刻):在此之前被采样的请求没有 span。每台机器为它的导出器保存一个项目最近的 2,048 个 span,和 OpenTelemetry SDK 的队列一样(在许多项目同时追踪请求的机器上会少一些);还没发送就被挤掉的,或等待超过十分钟的,计入导出目标的 status.signals.traces.dropped。
  • 请求进入时经过的边缘节点也会参与,作为平台的第一跳。它按这些设置决定每个请求在追踪中的参与方式(机器沿用它的决定,所以两者永远一致),并创建自己的 span(scope 为 sbx.edge),作为机器的 span(scope 为 sbx.request)的父级:从请求到达边缘节点开始,到响应的最后一个字节为止,命名和属性与机器的 span 相同,并把为请求寻找机器的过程记为事件:每台没有接收请求的机器一个 sbx.node.tried(sbx.node.said:unreached、full、not_there、no_answer),边缘节点等待得知应用的请求现在发往哪里时记一个 sbx.table.waited,以及 sbx.node.answered(sbx.node.tries,以及自请求到达起的 sbx.wait_ms)。它属于你的应用的资源,但不带实例。所以一个被采样的请求有边缘节点的 span,其下是机器的 span,再往下是你的 SDK 的 span;边缘节点的 span 开始与机器的 span 开始之间的时间,就是平台到达机器所用的时间。边缘节点不持有任何导出目标或凭据:它把完成的 span 交给响应该请求的机器,最多一秒后,由那台机器的导出器连同自己的 span 一起发送。边缘节点用来告诉机器这些信息的两个请求头(X-Sandbox-Trace、X-Sandbox-Spans)只供平台使用:边缘节点会从任何带着它们的请求中去掉它们,任何实例都收不到。(目前,带有键控实例应用的键的请求只有机器的 span。)

导出到 OpenTelemetry

导出目标从每台拥有这些数据的机器,发送项目中应用的日志、指标、追踪或其中任意几种,使用基于 HTTP 的 OTLP(http/protobuf 或 http/json,默认 gzip 压缩;不支持 gRPC):

POST /v1/destinations
{
  "name": "grafana",
  "endpoint": "https://otlp-gateway-prod-us-east-0.grafana.net/otlp",
  "headers": {"Authorization": "Basic ..."},
  "signals": ["logs", "metrics", "traces"],
  "kinds": ["output", "requests", "platform"],
  "apps": "all"
}
  • 每种信号发往 OTLP 的基础地址 (endpoint) 加上 /v1/logs、/v1/metrics 或 /v1/traces,或者发往它自己的地址(logs_endpoint、metrics_endpoint、traces_endpoint)。
  • 创建导出目标时,平台会向它发送一条记录(以及它接收的信号中的一个指标点、一个 span),只有接收端响应 2xx 时才会接入;POST /v1/destinations/{name}/test 可以随时再发一次。它的 status 说明发送情况(state:ok、retrying、failing、stopped、idle,或者在某次尝试得到 2xx 响应之前为 not_connected);destination.failing 和 destination.recovered 事件会在状态变化时通知。
  • status.today 统计今天 (UTC) 发给它的内容,以及没有发出去的内容和原因 (not_sent):它拒绝的 (rejected),以及在它接收之前就被放弃的:在日志行还算新鲜的十分钟内连不上它或它不接收 (destination_down);它接收得比日志产生得慢 (destination_slow);账户的出站流量已停止 (egress_stopped);日志所在的机器上没有运行导出器 (exporter_down);或者日志所在的机器已存满某个应用的日志而放弃它们,这发生在导出目标落后太多、超出机器能保存的时长时 (node_buffer)。被放弃的日志行会在应用的日志中留下一行 export.dropped,说明原因和数量,这一行只发给那个导出目标。
  • 请求头的值是凭据:加密保存,不再显示。
  • glac destinations create grafana --endpoint https://… --headers headers.txt 在命令行上创建一个导出目标(请求头的值从文件、标准输入或不回显的终端读取,从不从命令行读取,也没有任何命令会打印它),另有 ls、get、update、test 和 rm;SDK 中有 glac.destinations(create、get、list、update、delete、test),MCP 服务器的 project_get 会按名称显示它们。创建、修改、测试和删除导出目标需要有 admin 权限范围的密钥。
  • 账户在所有项目中能拥有的导出目标数量取决于它的套餐(见上表)。
  • 发往导出目标的内容属于发往互联网的流量:它计入套餐的流量额度,超出部分和其他流量一样计费,账户的出站流量停止时它也会停止。
导航

输入以搜索……

↑↓ 切换↵ 选择Esc 关闭