アプリのログ
アプリ(キューブ)のログは 1 つで、アプリを処理するすべてのマシンのログをまとめ、時刻順に並べたものです。行は 3 種類あります。
- output:インスタンスの起動コマンドが
stdoutとstderrに出力するもの - request:リクエストごとに 1 行。メソッド、パス、ステータス、所要時間、バイト数、リクエストの 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。行は 1 行に 1 つの 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)を示します。最長の行、1 つのインスタンスが 1 秒あたりに書ける行数、削除されたインスタンスについて保持されるものです。
glac cube log-usage api(GET /v1/apps/{app}/logs/usage)は、出力、保持、破棄された量とその理由を日ごとに数えます。glac cube log-delete api --before 2d(DELETE /v1/apps/{app}/logs)は、指定時刻より前の行を削除します。
**ログを読む人。**プロジェクトのキーと、問題を調べるときの Glac のオペレーターです。Glac のオペレーターがアプリのログを読むたびに、オペレーターに行が表示される前に、プロジェクトの監査ログ(GET /v1/audit)にエントリが書き込まれます。actor.type は operator で、log_read が、どのアプリか、読んだ期間(since、until)、新しい行を追跡したかどうかを示します。ログを保存してエクスポート先に送るプラットフォーム自身のプログラムは、ログを読む人にはあたらず、エントリを残しません。
**料金。**インスタンスの出力のうち保持されるものは、出力されたままのサイズ(圧縮して保存したサイズではなく)で、保持されている期間について数えられます。有料プランに含まれる月 10 GB-月 を超えた分は、1 GB-月あたり $0.02 です(料金とプランを参照)。つまり、1 か月に出力した 1 GB を 7 日間保持すると、約 0.25 GB-月になります。無料プランに料金はかからず、1 日に出力される 200 MB を保持します。リクエストの行、プラットフォームの行、保持しない出力は数えません。プランのログを使い切り、残高もなくなると、それ以降にアプリが出力するものは長期保存されません。しばらくはマシン上で読め、ログには logs.keep_off の行(reason: over_plan)で、保持が再開すると logs.keep_on の行で示されます。各マシンは、それを知った時点(通常は 1 秒以内。到達できないマシンでは、再び到達できるようになった時点)で保持をやめます。それまでに出力したものは従来どおり保持されて数えられ、そのうち残高で支払えない分は帳消しになり、未払いにはなりません(明細の written_off_usd)。
**現在の保持場所。**アプリの logs_state には、アプリのログを保持する各マシンについて、マシンが最後に報告した(数秒ごと)内容の項目があります。for_good は、今そこで出力されるものが長期保存される(オブジェクトストアに送られ、プランの日数だけ保持され、数えられる)かどうか、reason はされない場合の理由(over_plan、またはアプリの logs.keep が false の setting)、since はマシンの時計での開始時刻です。
メトリクス
GET /v1/apps/{app}/metrics は、直近 24 時間は 1 分ごと、30 日間は 1 時間ごとに、ステータスのクラス別のリクエスト、所要時間の 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)は、プラットフォームがスパンを作るリクエストの割合です。リクエストを受け取ったマシンがスパンを 1 つ作ります。リクエストのヘッダー部分の読み取りから、レスポンスの最後のバイトまでで、名前はメソッドです(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が届きます。SDK のスパンはプラットフォームのスパンの子になり、デフォルトのサンプラー(親に従う)を使う SDK は、プラットフォームがサンプリングしたリクエストをそのままサンプリングします。 trust_incoming(デフォルトは false)は、リクエスト自身のtraceparentを採用するかどうかです。true なら、プラットフォームのスパンは呼び出し元のトレースに加わり、その最初のスパンは呼び出し元のスパンの子になり、呼び出し元が指示すれば(割合にかかわらず)サンプリングされ、呼び出し元のtracestateも引き継がれます。false なら、各リクエストは独自のトレースを始めて割合に従ってサンプリングされ、その最初のスパンは呼び出し元のスパンにリンクします。呼び出し元のtracestateはインスタンスに届きません。信頼された呼び出し元は、何をサンプリングして送るかを決められるため、誰でもアクセスできるアプリでは false のままにしてください。- ログのリクエストの行にはトレース(
trace_id)が記録され、サンプリングされたリクエストではプラットフォームのスパン(span_id)も記録されるため、ログの行とスパンを互いにたどれます。エクスポートでは、これらはレコードのTraceIdとSpanIdになります。 - スパンは、
tracesを受け付けるプロジェクトのエクスポート先(後述)に、マシンがそれを把握した時点(作成直後)から送られます。それより前にサンプリングされたリクエストにはスパンがありません。各マシンは、OpenTelemetry SDK のキューと同じように、エクスポーター用にプロジェクトの直近 2,048 個のスパンを保持します(多くのプロジェクトが同時にリクエストをトレースするマシンでは、それより少なくなります)。送信前に押し出されたものや、10 分を超えて待ったものは、エクスポート先のstatus.signals.traces.droppedに数えられます。 - リクエストが入ってきたエッジも、プラットフォームの最初のホップとして参加します。エッジは、これらの設定に従って各リクエストのトレースでの扱いを決め(マシンはその決定に従うため、両者が食い違うことはありません)、独自のスパン(スコープ
sbx.edge)を作ります。これがマシンのスパン(スコープsbx.request)の親です。リクエストがエッジに届いてからレスポンスの最後のバイトまでで、名前と属性はマシンのものと同じです。リクエストのためにマシンを見つけるのにかかったことがイベントとして記録されます。引き受けなかった各マシンについてのsbx.node.tried(sbx.node.said:unreached、full、not_there、no_answer)、アプリのリクエストの現在の送り先を知るためにエッジが待った場合のsbx.table.waited、そしてsbx.node.answered(sbx.node.triesと、リクエストの到着からのsbx.wait_ms)です。このスパンは、インスタンスを含まないアプリのリソースに属します。つまり、サンプリングされたリクエストには、エッジのスパン、その子としてマシンのスパン、その下に SDK のスパンがあります。エッジのスパンの開始からマシンのスパンの開始までが、プラットフォームがマシンに届くまでにかかった時間です。エッジはエクスポート先も認証情報も持たず、完了したスパンを、遅くとも 1 秒後に、リクエストに応答したマシンに渡し、マシンのエクスポーターが自分のスパンと一緒に送ります。エッジがこれをマシンに伝える 2 つのヘッダー(X-Sandbox-Trace、X-Sandbox-Spans)はプラットフォーム専用で、エッジはそれらが付いて届いたリクエストから取り除き、インスタンスに届くことはありません。(キー別インスタンスを持つアプリのキーを運ぶリクエストには、今のところマシンのスパンしかありません。)
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)に送られます。 - 作成時に、プラットフォームはエクスポート先にレコードを 1 件(受け付けるシグナルに応じて、メトリクスの 1 点とスパン 1 つも)送り、レシーバーが 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)、あるいはエクスポート先が受け取る前に手放した場合で、行が十分に新しいうち(10 分)にエクスポート先に到達できなかったか受け取られなかった(destination_down)、届く速さより遅くしか受け取られなかった(destination_slow)、アカウントの送信トラフィックが止められていた(egress_stopped)、行のあるマシンでエクスポーターが動いていなかった(exporter_down)、またはマシンが 1 つのアプリについて保持できる上限に達して手放した(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スコープを持つキーが必要です。- アカウントが全プロジェクトで持てるエクスポート先の数は、プランによって決まります(上の表)。
- エクスポート先に送るものはインターネットへのトラフィックです。プランのトラフィックに数えられ、それを超えるとほかと同じく課金され、アカウントの送信トラフィックが止まると止まります。