应用的日志
一个应用(即一个方块)只有一份日志,由为它提供服务的每台机器的日志汇集而成,按时间排序。日志行有三种:
- 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 hoursGET /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 hourSDK 中是 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权限范围的密钥。- 账户在所有项目中能拥有的导出目标数量取决于它的套餐(见上表)。
- 发往导出目标的内容属于发往互联网的流量:它计入套餐的流量额度,超出部分和其他流量一样计费,账户的出站流量停止时它也会停止。