groundcover-cli

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Groundcover CLI

Groundcover CLI

Invoke the
groundcover
binary (install via
brew install paymog/tap/groundcover
). Source of truth is
paymog/groundcover-cli
.
调用
groundcover
二进制文件(可通过
brew install paymog/tap/groundcover
安装)。权威来源为
paymog/groundcover-cli

Auth (required before any command)

认证(所有命令执行前必须完成)

sh
export GROUNDCOVER_API_KEY=gcsa_...   # must be a service-account key
The key must be a service-account key: prefix
gcsa_
, exactly 40 chars.
Anything else is rejected with a clear message —
Invalid token prefix. Expected 'gcsa_'
or
Invalid token length (N, expected 40)
. Don't confuse key types:
  • gcsa_…
    service-account key. This is the one query/CRUD commands need.
  • gcik_…
    ingestion key (data push). Will NOT authenticate the API; wrong type.
A 401 with a correctly-formatted
gcsa_
key means the key belongs to a different tenant than the one you're querying.
If you don't have a
gcsa_
key handy, mint one with
groundcover service-accounts create
using an already-valid key, then
export GROUNDCOVER_API_KEY=gcsa_…
for the session.
Defaults baked in:
  • --base-url https://api.groundcover.com
  • --backend-id groundcover
No tenant UUID default — set
--tenant-uuid
/
GROUNDCOVER_TENANT_UUID
to send an
X-Tenant-UUID
header on non-grafana
raw …
passthrough calls (e.g. cross-tenant access). The embedded Grafana
raw grafana …
endpoints ignore it (see the Grafana section below).
The
raw grafana …
commands do NOT use the
gcsa_
key at all — they need a Grafana service account token (
glsa_…
). Set
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
/
--grafana-token
.
Override env:
GROUNDCOVER_BACKEND_ID
,
GROUNDCOVER_TENANT_UUID
,
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
,
GROUNDCOVER_BASE_URL
(or
GC_*
equivalents). Same names work as
--api-key
,
--backend-id
,
--tenant-uuid
,
--grafana-token
,
--base-url
flags.
sh
export GROUNDCOVER_API_KEY=gcsa_...   # 必须是服务账号密钥
密钥必须为服务账号密钥:前缀为
gcsa_
,长度恰好40个字符。
不符合要求的密钥会被拒绝,并返回明确提示信息——
Invalid token prefix. Expected 'gcsa_'
Invalid token length (N, expected 40)
。请勿混淆密钥类型:
  • gcsa_…
    服务账号密钥。查询/CRUD命令需使用此类密钥。
  • gcik_…
    数据摄入密钥(用于推送数据)。无法用于API认证,属于错误类型。
若格式正确的
gcsa_
密钥返回401错误,说明该密钥所属的租户与当前查询的租户不一致。
若手头没有
gcsa_
密钥,可使用已验证的密钥执行
groundcover service-accounts create
生成新密钥,然后在会话中执行
export GROUNDCOVER_API_KEY=gcsa_…
内置默认配置:
  • --base-url https://api.groundcover.com
  • --backend-id groundcover
无默认租户UUID——在非Grafana的
raw …
透传调用中(如跨租户访问),需设置
--tenant-uuid
/
GROUNDCOVER_TENANT_UUID
以发送
X-Tenant-UUID
请求头。嵌入式Grafana的
raw grafana …
端点会忽略该配置(见下文Grafana章节)。
raw grafana …
命令完全不使用
gcsa_
密钥
——它们需要Grafana服务账号令牌(
glsa_…
)。请设置
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
/
--grafana-token
可通过环境变量覆盖默认配置:
GROUNDCOVER_BACKEND_ID
GROUNDCOVER_TENANT_UUID
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
GROUNDCOVER_BASE_URL
(或等价的
GC_*
前缀变量)。这些变量名称与
--api-key
--backend-id
--tenant-uuid
--grafana-token
--base-url
标志一致。

Stored profiles (alternative to env vars)

存储配置文件(环境变量的替代方案)

Instead of exporting
GROUNDCOVER_API_KEY
every session, credentials can be saved in named profiles. The API key goes in the OS keyring; metadata (backend ID, base URL, tenant UUID) lives in
~/.config/groundcover/profiles.yaml
.
sh
groundcover auth login [name] --backend-id <id>   # prompts for key (or --key); validates before saving
groundcover auth list                              # * marks default
groundcover auth default <name>                    # change default
groundcover --profile <name> <command>             # use a profile for one command
groundcover auth status                            # show resolved source
groundcover auth token                             # print resolved key
groundcover auth logout <name> [-f]
Precedence:
--api-key
/
GROUNDCOVER_API_KEY
env →
--profile <name>
→ default profile. An explicit key plus
--profile
is rejected as ambiguous.
GROUNDCOVER_PROFILE
sets the profile via env.
Other global flags:
--timeout
(default 30s),
--raw
(don't reformat JSON response).
无需每次会话都导出
GROUNDCOVER_API_KEY
,可将凭证保存到命名配置文件中。API密钥存储在操作系统密钥环中;元数据(后端ID、基础URL、租户UUID)存储在
~/.config/groundcover/profiles.yaml
中。
sh
groundcover auth login [name] --backend-id <id>   # 提示输入密钥(或使用--key参数);保存前会验证有效性
groundcover auth list                              # *标记默认配置文件
groundcover auth default <name>                    # 修改默认配置文件
groundcover --profile <name> <command>             # 为单次命令指定配置文件
groundcover auth status                            # 显示已解析的凭证来源
groundcover auth token                             # 打印已解析的密钥
groundcover auth logout <name> [-f]
优先级:
--api-key
/
GROUNDCOVER_API_KEY
环境变量 →
--profile <name>
→ 默认配置文件。同时指定显式密钥和
--profile
会因歧义被拒绝。可通过
GROUNDCOVER_PROFILE
环境变量设置配置文件。
其他全局标志:
--timeout
(默认30秒)、
--raw
(不格式化JSON响应)。

Two surfaces

两种调用方式

SurfaceWhen to use
SDK-backed (
groundcover <resource> <verb>
)
First choice. Stable contracts, typed request bodies.
Raw HAR-derived (
groundcover raw …
)
Endpoint missing from SDK, or you need a runner feature SDK lacks (see below).
Always try the SDK form first.
方式使用场景
基于SDK (
groundcover <resource> <verb>
)
首选方式。契约稳定,请求体类型化。
基于原始HAR (
groundcover raw …
)
SDK未包含目标端点,或需要SDK不具备的运行器功能(见下文)。
优先尝试基于SDK的调用方式。

What raw gives you that SDK does not

原始方式具备的SDK未提供功能

  • --set dotted.path=value
    — deep-merge overrides on top of the body
  • --query key=value
    (repeatable) — arbitrary querystring overrides
  • Built-in default body captured from the webapp HAR (SDK requires explicit
    --body-file
    /
    --body-json
    )
  • Sends
    X-Tenant-UUID
    on non-grafana raw calls when you set
    --tenant-uuid
    /
    GROUNDCOVER_TENANT_UUID
    (for cross-tenant access; the SDK transport otherwise derives tenant from the API key). Grafana
    raw grafana …
    commands don't use it — they authenticate with the
    glsa_
    token instead.
  • groundcover raw list
    to discover every captured endpoint
Both surfaces support
--body-file
(json/yaml),
--body-json
, and
--raw
output.
  • --set dotted.path=value
    — 在请求体基础上进行深度合并覆盖
  • --query key=value
    (可重复) — 任意查询字符串覆盖
  • 内置从Web应用HAR捕获的默认请求体(SDK需要显式指定
    --body-file
    /
    --body-json
  • 当设置
    --tenant-uuid
    /
    GROUNDCOVER_TENANT_UUID
    时,非Grafana的原始调用会发送
    X-Tenant-UUID
    请求头(用于跨租户访问;SDK传输层会从API密钥中推导租户信息)。Grafana的
    raw grafana …
    命令不使用该配置——它们通过
    glsa_
    令牌进行认证。
  • groundcover raw list
    可发现所有已捕获的端点
两种方式均支持
--body-file
(json/yaml格式)、
--body-json
--raw
输出。

Endpoints that are raw-only (no SDK command yet)

仅支持原始方式的端点(暂无SDK命令)

Reach for
groundcover raw …
for any of these; the SDK has the parent resource but not the drilldown:
  • logs:
    filters
    ,
    velocity
    (SDK only has
    search
    )
  • traces:
    attributes
    ,
    details
    ,
    errors
    ,
    filters
    ,
    insights
    ,
    latencies
    ,
    requests
    ,
    values-distribution
    (SDK only has
    search
    )
  • metrics:
    cardinality
    ,
    cardinality-graph
    ,
    discovery
    ,
    labels-cardinality
    ,
    query-range
    ,
    resources errors|latencies|list|requests
    (SDK has
    query
    ,
    names
    ,
    keys
    ,
    values
    )
  • prometheus:
    prometheus api query
    (raw Prom passthrough — handy for ad-hoc PromQL via
    --query query='up'
    )
  • Grafana dashboards:
    grafana search
    ,
    grafana dashboards get|save|delete
    ,
    grafana dashboards permissions get|update
    ,
    grafana dashboards versions|get|restore
    ,
    grafana folders list|get|create|update|delete
    ,
    grafana folders permissions get|update
    ,
    grafana annotations list|create|update|delete
    ,
    grafana prometheus rules
    ,
    grafana datasources label-values
    ,
    grafana ds query
  • monitors drilldowns:
    instances filters|query|timeline
    ,
    labels keys
    ,
    silences
    ,
    summary filters|query
    ,
    timeline
    (SDK only has CRUD)
  • k8s drilldowns:
    configmaps|cronjob|daemonsets|deployments|jobs|pods|pvcs|replicasets|statefulsets list
    ,
    container info
    ,
    context events
    ,
    namespaces info|list
    ,
    nodes info-with-resources|list|resources|usage top10
    ,
    pod container usage
    ,
    pods status-over-time
    ,
    workloads availability|events|usage top10
    ,
    network connections|cross-az|cross-az-regions|partners|throughput|top-connections
    ,
    events search-time-series
    (SDK only has
    clusters
    ,
    workloads
    ,
    events-search
    ,
    events-over-time
    )
  • infra:
    infra hosts info-with-resources
  • resources / RUM:
    resources apis errors|filters|latencies|list|requests
    ,
    rum sessions filters|query
    ,
    sources list
  • pipelines stats:
    pipelines logs current-stats
    ,
    pipelines traces current-stats
    (SDK only does config CRUD)
  • tenant / billing / RBAC reads:
    rbac seatsUsage
    ,
    rbac tenant ai-settings
    ,
    rbac tenant settings
    ,
    backend settings
    ,
    billing method
    ,
    agent token-budgets|token-usage|token-usage history|token-usage tenant
  • storage management:
    storage-management get|update --data-type <logs|traces|events|measurements|monitor_instance>
    for retention, gcQL exception rules, and index-tier settings
  • misc:
    graph
    ,
    graph filters
    ,
    views member
    ,
    views member defaults
    ,
    migrations
    ,
    connectors list org|personal
    ,
    synthetics rules
    ,
    aggregations metrics config|config default
    ,
    integrations data config
Run
groundcover raw list
/
groundcover raw list <group>
to confirm the exact name before invoking.
以下场景需使用
groundcover raw …
;SDK仅包含父级资源,不支持细分操作:
  • 日志:
    filters
    velocity
    (SDK仅支持
    search
  • 链路追踪:
    attributes
    details
    errors
    filters
    insights
    latencies
    requests
    values-distribution
    (SDK仅支持
    search
  • 指标:
    cardinality
    cardinality-graph
    discovery
    labels-cardinality
    query-range
    resources errors|latencies|list|requests
    (SDK支持
    query
    names
    keys
    values
  • Prometheus:
    prometheus api query
    (原始Prom透传——便于通过
    --query query='up'
    执行临时PromQL查询)
  • Grafana仪表板:
    grafana search
    grafana dashboards get|save|delete
    grafana dashboards permissions get|update
    grafana dashboards versions|get|restore
    grafana folders list|get|create|update|delete
    grafana folders permissions get|update
    grafana annotations list|create|update|delete
    grafana prometheus rules
    grafana datasources label-values
    grafana ds query
  • 监控器细分操作:
    instances filters|query|timeline
    labels keys
    silences
    summary filters|query
    timeline
    (SDK仅支持CRUD)
  • K8s细分操作:
    configmaps|cronjob|daemonsets|deployments|jobs|pods|pvcs|replicasets|statefulsets list
    container info
    context events
    namespaces info|list
    nodes info-with-resources|list|resources|usage top10
    pod container usage
    pods status-over-time
    workloads availability|events|usage top10
    network connections|cross-az|cross-az-regions|partners|throughput|top-connections
    events search-time-series
    (SDK仅支持
    clusters
    workloads
    events-search
    events-over-time
  • 基础设施:
    infra hosts info-with-resources
  • 资源/RUM:
    resources apis errors|filters|latencies|list|requests
    rum sessions filters|query
    sources list
  • 流水线统计:
    pipelines logs current-stats
    pipelines traces current-stats
    (SDK仅支持配置CRUD)
  • 租户/计费/RBAC读取:
    rbac seatsUsage
    rbac tenant ai-settings
    rbac tenant settings
    backend settings
    billing method
    agent token-budgets|token-usage|token-usage history|token-usage tenant
  • 存储管理:
    storage-management get|update --data-type <logs|traces|events|measurements|monitor_instance>
    用于配置保留策略、gcQL例外规则和索引层级设置
  • 其他:
    graph
    graph filters
    views member
    views member defaults
    migrations
    connectors list org|personal
    synthetics rules
    aggregations metrics config|config default
    integrations data config
调用前可执行
groundcover raw list
/
groundcover raw list <group>
确认准确名称。

SDK-backed CRUD pattern

基于SDK的CRUD模式

sh
groundcover <resource> list   [--query '<filter>']
groundcover <resource> get    <id>
groundcover <resource> create --body-file <file>   # or --body-json '<json>'
groundcover <resource> update <id> --body-file <file>
groundcover <resource> delete <id>
Body files:
.json
or
.yaml
.
sh
groundcover <resource> list   [--query '<filter>']
groundcover <resource> get    <id>
groundcover <resource> create --body-file <file>   # 或--body-json '<json>'
groundcover <resource> update <id> --body-file <file>
groundcover <resource> delete <id>
请求体文件支持
.json
.yaml
格式。

Resources (all follow the pattern above)

资源(均遵循上述模式)

  • dashboards — also
    archive <id>
    ,
    restore <id>
  • monitors
    --query 'monitor_name = "cpu"'
  • silences
    list --active
  • recurring-silences
  • connected-apps
    --query 'type:slack-webhook'
  • notification-routes
    --query 'prod'
  • synthetics
  • secrets — also
    hash <id>
  • workflows
  • dashboards — 额外支持
    archive <id>
    restore <id>
  • monitors — 支持
    --query 'monitor_name = "cpu"'
  • silences — 支持
    list --active
  • recurring-silences
  • connected-apps — 支持
    --query 'type:slack-webhook'
  • notification-routes — 支持
    --query 'prod'
  • synthetics
  • secrets — 额外支持
    hash <id>
  • workflows

Auth / RBAC

认证/RBAC

sh
groundcover api-keys list
groundcover api-keys create --body-file key.json
groundcover service-accounts list
groundcover service-accounts create --body-file sa.json
groundcover ingestion-keys list
groundcover policies list
groundcover policies apply --body-file policy.json
groundcover policies audit-trail <id>
sh
groundcover api-keys list
groundcover api-keys create --body-file key.json
groundcover service-accounts list
groundcover service-accounts create --body-file sa.json
groundcover ingestion-keys list
groundcover policies list
groundcover policies apply --body-file policy.json
groundcover policies audit-trail <id>

Pipeline singletons (
get
/
create
/
update
/
delete
, no id)

流水线单例(仅支持
get
/
create
/
update
/
delete
,无需ID)

sh
groundcover logs-pipeline get
groundcover metrics-pipeline get
groundcover traces-pipeline get
groundcover metrics-aggregator get
sh
groundcover logs-pipeline get
groundcover metrics-pipeline get
groundcover traces-pipeline get
groundcover metrics-aggregator get

Integrations (typed)

集成(类型化)

sh
groundcover integrations list
groundcover integrations describe <type>
groundcover integrations create <type>      --body-file config.json
groundcover integrations update <type> <id> --body-file config.json
sh
groundcover integrations list
groundcover integrations describe <type>
groundcover integrations create <type>      --body-file config.json
groundcover integrations update <type> <id> --body-file config.json

Observability / read commands (body-file driven)

可观测性/读取命令(基于请求体文件)

sh
groundcover logs search          --body-file body.json
groundcover traces search        --body-file body.json
groundcover metrics query        --body-file body.json   # PromQL range/instant
groundcover metrics names        --body-file body.json   # list metric names
groundcover metrics keys         --body-file body.json   # label keys for a metric
groundcover metrics values       --body-file body.json   # label values for a key
groundcover search discovery     --body-file body.json   # facet / discovery
groundcover search keys          --body-file body.json
groundcover search values        --body-file body.json
groundcover k8s clusters         --body-file body.json
groundcover k8s workloads        --body-file body.json
groundcover k8s events-search    --body-file body.json
groundcover k8s events-over-time --body-file body.json
Pipe to
jq
to extract fields, or pass
--raw
to dump the raw response.
sh
groundcover logs search          --body-file body.json
groundcover traces search        --body-file body.json
groundcover metrics query        --body-file body.json   # PromQL范围/即时查询
groundcover metrics names        --body-file body.json   # 列出指标名称
groundcover metrics keys         --body-file body.json   # 指标的标签键
groundcover metrics values       --body-file body.json   # 标签键对应的标签值
groundcover search discovery     --body-file body.json   # 分面/发现
groundcover search keys          --body-file body.json
groundcover search values        --body-file body.json
groundcover k8s clusters         --body-file body.json
groundcover k8s workloads        --body-file body.json
groundcover k8s events-search    --body-file body.json
groundcover k8s events-over-time --body-file body.json
可通过管道传递给
jq
提取字段,或使用
--raw
参数输出原始响应。

Body templates for observability

可观测性请求体模板

Times are RFC 3339 UTC (
2026-05-28T00:00:00Z
). Wall-clock-now isn't built-in — compute with
date -u
. All searches share the same shape (
logs search
,
traces search
,
k8s events-search
):
json
{
  "start": "2026-05-28T00:00:00Z",
  "end":   "2026-05-28T01:00:00Z",
  "query": "<GCQL>",
  "filters": "",
  "sources": []
}
metrics query
(note the capitalized field names — this is the API):
json
{
  "Promql":    "rate(http_requests_total[5m])",
  "Start":     "2026-05-28T00:00:00Z",
  "End":       "2026-05-28T01:00:00Z",
  "Step":      "60",
  "QueryType": "range",
  "Conditions": []
}
Use
"QueryType": "instant"
and omit
Step
for a point query.
k8s workloads
/
k8s clusters
:
json
{
  "conditions":  [],
  "sources":     [],
  "gcqlFilter":  "namespace = 'prod'",
  "limit":       100
}
时间格式为RFC 3339 UTC(
2026-05-28T00:00:00Z
)。未内置当前时间功能——需使用
date -u
计算。所有搜索请求格式一致(
logs search
traces search
k8s events-search
):
json
{
  "start": "2026-05-28T00:00:00Z",
  "end":   "2026-05-28T01:00:00Z",
  "query": "<GCQL>",
  "filters": "",
  "sources": []
}
metrics query
(注意字段名称首字母大写——这是API要求):
json
{
  "Promql":    "rate(http_requests_total[5m])",
  "Start":     "2026-05-28T00:00:00Z",
  "End":       "2026-05-28T01:00:00Z",
  "Step":      "60",
  "QueryType": "range",
  "Conditions": []
}
使用
"QueryType": "instant"
并省略
Step
可执行点查询。
k8s workloads
/
k8s clusters
json
{
  "conditions":  [],
  "sources":     [],
  "gcqlFilter":  "namespace = 'prod'",
  "limit":       100
}

Raw commands

原始命令

sh
groundcover raw list                          # list groups
groundcover raw list <group>                  # list commands in group
groundcover raw k8s clusters list
groundcover raw dashboards get --dashboard-id <id>
groundcover raw metrics query-range --body-file body.json
groundcover raw prometheus api query --query query='up'
groundcover raw grafana dashboards get --dashboard-uid <uid>
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana ds query --body-file query.json
sh
groundcover raw list                          # 列出分组
groundcover raw list <group>                  # 列出分组内的命令
groundcover raw k8s clusters list
groundcover raw dashboards get --dashboard-id <id>
groundcover raw metrics query-range --body-file body.json
groundcover raw prometheus api query --query query='up'
groundcover raw grafana dashboards get --dashboard-uid <uid>
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana ds query --body-file query.json

Storage management

存储管理

Admins can read and update lifecycle settings for
logs
,
traces
,
events
,
measurements
(APM), and
monitor_instance
(monitor issues):
sh
groundcover raw storage-management get --data-type logs --raw \
  | jq '{retention,version,cold_move_duration,cold_volume,custom_rules:(.custom_rules // [])}' \
  > storage.json
管理员可读取和更新
logs
traces
events
measurements
(APM)和
monitor_instance
(监控问题)的生命周期设置:
sh
groundcover raw storage-management get --data-type logs --raw \
  | jq '{retention,version,cold_move_duration,cold_volume,custom_rules:(.custom_rules // [])}' \
  > storage.json

Edit storage.json, preserving every writable field and the complete rule list.

编辑storage.json,保留所有可写字段和完整规则列表。

groundcover raw storage-management update --data-type logs --body-file storage.json

Updates use optimistic concurrency and replace the writable settings document. Always start from `get`, carry forward its current `version`, and preserve `retention`, `cold_move_duration`, `cold_volume`, and the complete `custom_rules` list; omitting `custom_rules` removes existing rules. A successful update increments `version`. Each observed custom rule contains `name`, `retention`, and a gcQL `filters` expression.
groundcover raw storage-management update --data-type logs --body-file storage.json

更新使用乐观并发机制,会替换可写设置文档。请始终从`get`命令获取当前设置,保留其`version`值,并维护`retention`、`cold_move_duration`、`cold_volume`和完整的`custom_rules`列表;省略`custom_rules`会删除现有规则。更新成功后`version`值会递增。每条自定义规则包含`name`、`retention`和gcQL`filters`表达式。

Grafana native dashboards

Grafana原生仪表板

Groundcover also embeds Grafana at
/grafana
. These are not the same as Groundcover's first-class
dashboards
SDK resource, so use
raw grafana …
when you need native Grafana JSON dashboards, folders, permissions, annotations, datasource-backed variable values, or panel query execution.
Auth: the embedded Grafana lives behind a session-gated proxy that ignores the
gcsa_
API key, backend ID, and tenant UUID. A bearer/
gcsa_
request to
/grafana/api/*
just returns the ~980KB Grafana SPA
index.html
(HTTP 200,
text/html
), never JSON. These commands require a Grafana service account token (
glsa_…
) instead: set
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
(or
--grafana-token
). Run any
raw grafana …
command without it and the CLI prints a full setup guide.
Generating a token (needs a groundcover tenant admin). The token comes from groundcover's official CLI (
github.com/groundcover-com/cli
), which unfortunately also installs a binary named
groundcover
. Its installer drops it at
~/.groundcover/bin/groundcover
and prepends that dir to your PATH, so afterwards
groundcover
resolves to the official CLI and shadows this one. Invoke the official binary by full path to avoid the collision:
sh
sh -c "$(curl -fsSL https://groundcover.com/install.sh)"      # installs to ~/.groundcover/bin
~/.groundcover/bin/groundcover auth login                     # auth flow
~/.groundcover/bin/groundcover auth generate-service-account-token   # prints glsa_… once
export GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN=glsa_...     # hand it to this CLI
To keep this CLI as
groundcover
, remove the
~/.groundcover/bin
PATH line the installer adds to your shell rc.
Common commands:
sh
groundcover raw grafana search --query query='service slo' --query folderUIDs=general
groundcover raw grafana dashboards get --dashboard-uid streamling-pipeline-slo
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana datasources label-values --datasource-uid <uid> --label project_id --query start=<unix> --query end=<unix>
groundcover raw grafana ds query --query ds_type=prometheus --body-file query.json
Grafana raw commands default to
https://app.groundcover.com
(the webapp host), while normal API/SDK commands default to
https://api.groundcover.com
. Pass
--base-url
only for non-standard deployments.
Raw flags:
--body-json
,
--body-file
,
--set dotted.path=value
,
--query key=value
(repeatable), generated path flags (e.g.
--dashboard-id
),
--raw
. Many raw commands ship a default body captured from the webapp HAR, so you can run them with no
--body-*
at all and override specific fields with
--set
.
Groundcover在
/grafana
路径嵌入了Grafana。这些仪表板不同于Groundcover的一等资源
dashboards
(SDK资源),因此当需要原生Grafana JSON仪表板、文件夹、权限、注释、数据源支持的变量值或面板查询执行时,请使用
raw grafana …
命令。
认证: 嵌入式Grafana位于会话网关代理之后,会忽略
gcsa_
API密钥、后端ID和租户UUID。使用Bearer/
gcsa_
请求访问
/grafana/api/*
仅会返回约980KB的Grafana SPA
index.html
(HTTP 200,
text/html
),不会返回JSON。这些命令需要Grafana服务账号令牌(
glsa_…
):请设置
GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN
(或
--grafana-token
)。未设置令牌时执行
raw grafana …
命令,CLI会打印完整的设置指南。
生成令牌(需要Groundcover租户管理员权限)。令牌来自Groundcover官方CLI(
github.com/groundcover-com/cli
),该CLI安装的二进制文件也名为
groundcover
。其安装程序会将文件放置在
~/.groundcover/bin/groundcover
并将该目录添加到PATH中,之后
groundcover
会指向官方CLI并覆盖当前CLI。请通过完整路径调用官方二进制文件以避免冲突:
sh
sh -c "$(curl -fsSL https://groundcover.com/install.sh)"      # 安装到~/.groundcover/bin
~/.groundcover/bin/groundcover auth login                     # 认证流程
~/.groundcover/bin/groundcover auth generate-service-account-token   # 打印一次glsa_…令牌
export GROUNDCOVER_GRAFANA_SERVICE_ACCOUNT_TOKEN=glsa_...     # 将令牌提供给当前CLI
若要将当前CLI保留为
groundcover
,请删除安装程序添加到shell配置文件中的
~/.groundcover/bin
PATH配置。
常用命令:
sh
groundcover raw grafana search --query query='service slo' --query folderUIDs=general
groundcover raw grafana dashboards get --dashboard-uid streamling-pipeline-slo
groundcover raw grafana dashboards save --body-file dashboard.json
groundcover raw grafana folders list
groundcover raw grafana datasources label-values --datasource-uid <uid> --label project_id --query start=<unix> --query end=<unix>
groundcover raw grafana ds query --query ds_type=prometheus --body-file query.json
Grafana原始命令默认使用
https://app.groundcover.com
(Web应用主机),而普通API/SDK命令默认使用
https://api.groundcover.com
。仅在非标准部署场景下需要传递
--base-url
参数。
原始命令标志:
--body-json
--body-file
--set dotted.path=value
--query key=value
(可重复)、生成的路径标志(如
--dashboard-id
)、
--raw
。许多原始命令内置从Web应用HAR捕获的默认请求体,因此无需指定
--body-*
参数即可运行,并可通过
--set
覆盖特定字段。

Recipes

实用示例

Last hour of error logs for a service

某服务最近一小时的错误日志

sh
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)   # GNU: date -u -d '1 hour ago' +%FT%TZ
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND level = \"error\"",
  filters: "", sources: []
}' | groundcover logs search --body-file /dev/stdin | jq '.[] | {ts:.timestamp, pod:.pod_name, body:(.body|fromjson?)}'
logs search
returns a bare JSON array at the root
jq '.[]'
, not
.logs[]
. The human-readable message lives in the
body
field as a JSON string (parse with
fromjson
). Useful top-level fields:
pod_name
,
container_name
,
namespace
,
workload
,
level
(normalized lowercase —
error
/
warning
/
debug
, not the
WARN
/
DEBG
you see inside
body
),
cluster
,
timestamp
,
trace_id
.
sh
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)   # GNU系统:date -u -d '1 hour ago' +%FT%TZ
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND level = \"error\"",
  filters: "", sources: []
}' | groundcover logs search --body-file /dev/stdin | jq '.[] | {ts:.timestamp, pod:.pod_name, body:(.body|fromjson?)}'
logs search
返回的是顶层裸JSON数组
— 使用
jq '.[]'
而非
.logs[]
。可读消息位于**
body
**字段中,为JSON字符串(需使用
fromjson
解析)。常用顶层字段:
pod_name
container_name
namespace
workload
level
(标准化小写——
error
/
warning
/
debug
,而非
body
字段中的
WARN
/
DEBG
)、
cluster
timestamp
trace_id

p99 latency for a workload (last 30m, PromQL)

某工作负载的p99延迟(最近30分钟,PromQL)

sh
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  Promql: "histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{service=\"api\"}[5m])))",
  Start: $s, End: $e, Step: "60", QueryType: "range", Conditions: []
}' | groundcover metrics query --body-file /dev/stdin
sh
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  Promql: "histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{service=\"api\"}[5m])))",
  Start: $s, End: $e, Step: "60", QueryType: "range", Conditions: []
}' | groundcover metrics query --body-file /dev/stdin

Crashlooping or restarting pods

处于崩溃循环或重启状态的Pod

sh
jq -n '{ conditions: [], sources: [], gcqlFilter: "status != \"Running\"", limit: 200 }' \
  | groundcover k8s workloads --body-file /dev/stdin \
  | jq '.workloads[] | select(.restarts > 0) | {name, namespace, status, restarts}'
sh
jq -n '{ conditions: [], sources: [], gcqlFilter: "status != \"Running\"", limit: 200 }' \
  | groundcover k8s workloads --body-file /dev/stdin \
  | jq '.workloads[] | select(.restarts > 0) | {name, namespace, status, restarts}'

Recent k8s events for a namespace

某命名空间的近期K8s事件

sh
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "namespace = \"prod\" AND type = \"Warning\"",
  filters: "", sources: []
}' | groundcover k8s events-search --body-file /dev/stdin
sh
END=$(date -u +%FT%TZ); START=$(date -u -v-1H +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "namespace = \"prod\" AND type = \"Warning\"",
  filters: "", sources: []
}' | groundcover k8s events-search --body-file /dev/stdin

Find slow traces, then pivot to their logs

查找慢链路追踪,然后关联到对应的日志

sh
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND duration_ms > 1000",
  filters: "", sources: []
}' | groundcover traces search --body-file /dev/stdin \
  | jq -r '.[] | .trace_id' | head -5 \
  | while read tid; do
      jq -n --arg s "$START" --arg e "$END" --arg t "$tid" '{
        start:$s, end:$e, query: ("trace_id = \"" + $t + "\""), filters: "", sources: []
      }' | groundcover logs search --body-file /dev/stdin
    done
sh
END=$(date -u +%FT%TZ); START=$(date -u -v-30M +%FT%TZ)
jq -n --arg s "$START" --arg e "$END" '{
  start:$s, end:$e,
  query: "service_name = \"api\" AND duration_ms > 1000",
  filters: "", sources: []
}' | groundcover traces search --body-file /dev/stdin \
  | jq -r '.[] | .trace_id' | head -5 \
  | while read tid; do
      jq -n --arg s "$START" --arg e "$END" --arg t "$tid" '{
        start:$s, end:$e, query: ("trace_id = \"" + $t + "\""), filters: "", sources: []
      }' | groundcover logs search --body-file /dev/stdin
    done

Discover what labels/fields exist on a stream

发现某数据流存在的标签/字段

sh
undefined
sh
undefined

What metric names match a prefix?

哪些指标名称匹配指定前缀?

jq -n '{ prefix: "http_" }' | groundcover metrics names --body-file /dev/stdin
jq -n '{ prefix: "http_" }' | groundcover metrics names --body-file /dev/stdin

What labels does a specific metric have?

某特定指标有哪些标签?

jq -n '{ metric: "http_request_duration_seconds" }' | groundcover metrics keys --body-file /dev/stdin
jq -n '{ metric: "http_request_duration_seconds" }' | groundcover metrics keys --body-file /dev/stdin

What values does a label take?

某标签有哪些取值?

jq -n '{ metric: "http_request_duration_seconds", key: "service" }'
| groundcover metrics values --body-file /dev/stdin
undefined
jq -n '{ metric: "http_request_duration_seconds", key: "service" }'
| groundcover metrics values --body-file /dev/stdin
undefined

GCQL quick reference (logs/traces/events queries)

GCQL快速参考(日志/链路追踪/事件查询)

  • Equality:
    service_name = "api"
    (double quotes inside JSON →
    \"
    )
  • Comparison:
    duration_ms > 1000
    ,
    status_code >= 500
  • Boolean:
    AND
    ,
    OR
    ,
    NOT
  • Substring:
    message ~ "timeout"
    , regex:
    message ~* "^panic:"
  • Membership:
    level IN ("error", "fatal")
    — works on a named field only
  • Freetext phrase: a bare quoted string, e.g.
    container = "my-service" AND "database unavailable"
    .
    IN(...)
    is not valid as freetext (errors
    freetext 'in(...)' is not supported
    ) — expand to
    level = "error" OR level = "fatal"
    .
  • Never send an empty
    query
    query: ""
    → 500
    pipeline cannot be nil
    . Always supply at least one term.
  • Null check:
    trace_id IS NOT NULL
Enumerating fields:
search discovery
/
search keys
are finicky (discovery requires
Limit
+
Type
and may still 400). The reliable move is to fetch one sample log and inspect
keys
and the parsed
body
.
  • 相等判断:
    service_name = "api"
    (JSON中需转义双引号为
    \"
  • 比较判断:
    duration_ms > 1000
    status_code >= 500
  • 布尔运算:
    AND
    OR
    NOT
  • 子字符串匹配:
    message ~ "timeout"
    ,正则匹配:
    message ~* "^panic:"
  • 成员判断:
    level IN ("error", "fatal")
    — 仅适用于命名字段
  • 自由文本短语: 裸引号字符串,例如
    container = "my-service" AND "database unavailable"
    IN(...)
    不能作为自由文本使用(会报错
    freetext 'in(...)' is not supported
    )——需展开为
    level = "error" OR level = "fatal"
  • 切勿发送空
    query
    query: ""
    会返回500错误
    pipeline cannot be nil
    。请始终至少提供一个查询条件。
  • 空值检查:
    trace_id IS NOT NULL
枚举字段:
search discovery
/
search keys
操作较为繁琐(discovery需要
Limit
+
Type
参数,仍可能返回400错误)。可靠的方法是获取一条日志样本并检查
keys
和解析后的
body
字段。

Find then silence a monitor

查找并静默某监控器

sh
groundcover monitors list --query 'monitor_name ~ "ingest"'
groundcover silences create --body-json '{"monitor_id":"<id>","duration_seconds":3600,"reason":"deploy"}'
sh
groundcover monitors list --query 'monitor_name ~ "ingest"'
groundcover silences create --body-json '{"monitor_id":"<id>","duration_seconds":3600,"reason":"deploy"}'

Dump every dashboard

导出所有仪表板

The list field is
uuid
, not
id
(
.id
is null). Each entry also has
archivedTimestamp
— skip archived ones (active =
0001-01-01T00:00:00.000Z
).
sh
groundcover dashboards list \
  | jq -r '.[] | select(.archivedTimestamp == "0001-01-01T00:00:00.000Z") | .uuid' \
  | while read id; do groundcover dashboards get "$id" > "dashboards/$id.json"; done
列表字段为**
uuid
**,而非
id
.id
为null)。每个条目还包含
archivedTimestamp
——跳过已归档的仪表板(活跃仪表板的该值为
0001-01-01T00:00:00.000Z
)。
sh
groundcover dashboards list \
  | jq -r '.[] | select(.archivedTimestamp == "0001-01-01T00:00:00.000Z") | .uuid' \
  | while read id; do groundcover dashboards get "$id" > "dashboards/$id.json"; done

Build a dashboard deep-link with pre-filled template variables

构建带预填充模板变量的仪表板深度链接

groundcover dashboards get <uuid>
returns the variable definitions under
.preset
(a JSON string — parse with
fromjson
). Find a dashboard and inspect its vars:
sh
groundcover dashboards list | jq -r '.[] | select(.name|test("prod";"i")) | "\(.uuid)\t\(.name)"'
groundcover dashboards get <uuid> | jq -r '.preset | fromjson | .variables'
groundcover dashboards get <uuid>
返回的仪表板中,
.preset
字段为JSON字符串(需使用
fromjson
解析;写回时需重新序列化为字符串)。查找仪表板并检查其变量:
sh
groundcover dashboards list | jq -r '.[] | select(.name|test("prod";"i")) | "\(.uuid)\t\(.name)"'
groundcover dashboards get <uuid> | jq -r '.preset | fromjson | .variables'

each var: { kind:"list", spec:{ values:{default:["*"]}, datasource:{key,kind,metric}, variableName } }

每个变量格式:{ kind:"list", spec:{ values:{default:["*"]}, datasource:{key,kind,metric}, variableName } }

The webapp encodes selected variable values in a `variables` **URL query param** — a URL-encoded JSON object keyed by `$<variableName>`. Each entry needs `name`, `key`, `values` (array; supports globs like `myapp*`), `datasource` (usually `"metrics"`), and `refiner.metric` (the source metric from the var's `datasource.metric`).

**Pin EVERY variable you care about — including ones you want wide open.** Any var you omit falls back to the dashboard's *saved default*, which is often NOT `*` (e.g. a hardcoded `deployment` hash, or `shard: primary`). To get a truly broad view, explicitly set those to `["*"]`. Always confirm a label value exists before using it — e.g. `app_kubernetes_io_instance` may have no bare `myapp`, only `myapp-web-0` etc., so use the `myapp*` glob.

Build it with jq so the encoding is correct:
```sh
VARS=$(jq -cn '{
  "$cluster":{name:"cluster",key:"cluster",values:["example-cluster"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$deployment":{name:"deployment",key:"deployment",values:["*"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$app_kubernetes_io_instance":{name:"app_kubernetes_io_instance",key:"app_kubernetes_io_instance",values:["myapp*"],datasource:"metrics",refiner:{metric:"my_metric_total"}}
}')
ENC=$(jq -rn --arg v "$VARS" '$v|@uri')
echo "https://app.groundcover.com/dashboards?backendId=groundcover&tenantUUID=<your-tenant-uuid>&duration=Last+1+hour&viewId=<uuid>&qb-disable-auto-focus=true&variables=${ENC}"
Notes: the dashboard opens via
viewId=<uuid>
(not the
/dashboards/<uuid>
path);
duration
takes UI strings like
Last+1+hour
/
Last+5+minutes
.
Web应用会将选中的变量值编码到`variables`**URL查询参数**中——这是一个URL编码的JSON对象,键为`$<variableName>`。每个条目需要`name`、`key`、`values`(数组;支持通配符如`myapp*`)、`datasource`(通常为`"metrics"`)和`refiner.metric`(来自变量`datasource.metric`的源指标)。

**固定所有你关心的变量——包括那些你想要完全开放的变量。** 任何未指定的变量会回退到仪表板的**保存默认值**,而默认值通常不是`*`(例如硬编码的`deployment`哈希值,或`shard: primary`)。若要获得真正全面的视图,请显式将这些变量设置为`["*"]`。使用前请确认标签值存在——例如`app_kubernetes_io_instance`可能没有裸值`myapp`,只有`myapp-web-0`等,因此请使用`myapp*`通配符。

使用jq构建链接以确保编码正确:
```sh
VARS=$(jq -cn '{
  "$cluster":{name:"cluster",key:"cluster",values:["example-cluster"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$deployment":{name:"deployment",key:"deployment",values:["*"],datasource:"metrics",refiner:{metric:"my_metric_total"}},
  "$app_kubernetes_io_instance":{name:"app_kubernetes_io_instance",key:"app_kubernetes_io_instance",values:["myapp*"],datasource:"metrics",refiner:{metric:"my_metric_total"}}
}')
ENC=$(jq -rn --arg v "$VARS" '$v|@uri')
echo "https://app.groundcover.com/dashboards?backendId=groundcover&tenantUUID=<your-tenant-uuid>&duration=Last+1+hour&viewId=<uuid>&qb-disable-auto-focus=true&variables=${ENC}"
注意:仪表板通过
viewId=<uuid>
打开(而非
/dashboards/<uuid>
路径);
duration
接受UI字符串如
Last+1+hour
/
Last+5+minutes

Ad-hoc Prom query via raw

通过原始命令执行临时Prom查询

sh
groundcover raw prometheus api query --query query='up{namespace="prod"}'
sh
groundcover raw prometheus api query --query query='up{namespace="prod"}'

Building & editing dashboards

构建与编辑仪表板

The interesting part of a dashboard isn't the CRUD verbs (
dashboards get/list/create/update/archive/restore
) — it's the preset, where the whole panel layout lives in one field.
仪表板的核心并非CRUD操作(
dashboards get/list/create/update/archive/restore
)——而是preset字段,整个面板布局都存储在该字段中。

Preset structure

Preset结构

dashboards get <uuid>
returns the dashboard with
.preset
as a JSON string (parse with
fromjson
/
json.loads
; re-serialize to a string before writing back). Shape:
jsonc
{
  "spec": { "layoutType": "ordered", "crosshairSyncEnabled": true },
  "layout": [ /* one entry per TOP-LEVEL item, placed on a 24-col grid */ ],
  "widgets": [ /* the actual panels, referenced by id from layout */ ],
  "duration": "Last 6 hours",   // UI duration string
  "variables": [ /* template vars, same shape as the deep-link recipe */ ],
  "schemaVersion": 7
}
  • layout
    — top-level grid placement. Each entry
    { "id":"A", "h":12, "w":24, "x":0, "y":0, "minH":8 }
    . Grid is 24 columns wide (
    w:8
    = a third,
    w:12
    = half);
    id
    must match a widget
    id
    . You hand-compute
    y
    to stack rows — there is no auto-flow.
  • widgets
    { id, name, type, queries:[{id, expr, dataType:"metrics", editorMode:"editor"}], visualizationConfig:{ type, selectedUnit, ... } }
    .
    expr
    is PromQL with
    $var
    substitutions.
  • Widget types:
    time-series
    ,
    stat
    ,
    text
    (markdown in
    content
    , no queries — use for headers/dividers),
    section
    .
dashboards get <uuid>
返回的仪表板中,
.preset
JSON字符串(需使用
fromjson
/
json.loads
解析;写回时需重新序列化为字符串)。结构如下:
jsonc
{
  "spec": { "layoutType": "ordered", "crosshairSyncEnabled": true },
  "layout": [ /* 每个顶层条目,放置在24列网格上 */ ],
  "widgets": [ /* 实际面板,通过id与layout关联 */ ],
  "duration": "Last 6 hours",   // UI时长字符串
  "variables": [ /* 模板变量,与深度链接示例中的格式一致 */ ],
  "schemaVersion": 7
}
  • layout
    — 顶层网格布局。每个条目格式为
    { "id":"A", "h":12, "w":24, "x":0, "y":0, "minH":8 }
    。网格宽度为24列
    w:8
    表示三分之一宽度,
    w:12
    表示一半宽度);
    id
    必须与widget的
    id
    匹配。需手动计算
    y
    值来堆叠行——无自动流布局。
  • widgets
    — 格式为
    { id, name, type, queries:[{id, expr, dataType:"metrics", editorMode:"editor"}], visualizationConfig:{ type, selectedUnit, ... } }
    expr
    为带
    $var
    替换的PromQL表达式。
  • Widget类型:
    time-series
    stat
    text
    content
    字段为markdown,无查询——用于标题/分隔符)、
    section

Sections (collapsible groups)

分组(可折叠)

A section is a widget
{ id, name, type:"section", color, isCollapsed:false }
. Its layout entry carries a
children
array of relative-positioned panel entries:
{ id, h, w:24, x:0, y:0, children:[ {h,w,x,y,id,minH}, ... ] }
. The header occupies the top ~2 rows, so children start at
y:2
.
分组是类型为
section
的widget:
{ id, name, type:"section", color, isCollapsed:false }
。其layout条目包含
children
数组,存储相对定位的面板条目:
{ id, h, w:24, x:0, y:0, children:[ {h,w,x,y,id,minH}, ... ] }
。标题占据顶部约2行,因此子项需从
y:2
开始。

The update contract

更新契约

dashboards update <uuid>
body MUST include
currentRevision
(the current
.revisionNumber
) — it's optimistic concurrency. Omit it →
Field validation for 'CurrentRevision' failed on the 'required_if' tag
. Re-
get
after every successful update to read the bumped revision before the next write.
sh
groundcover dashboards get <uuid> > d.json
dashboards update <uuid>
的请求体必须包含
currentRevision
(当前
.revisionNumber
)——这是乐观并发机制。省略该字段会返回
Field validation for 'CurrentRevision' failed on the 'required_if' tag
。每次更新成功后需重新执行
get
命令获取更新后的版本号,再进行下一次写入。
sh
groundcover dashboards get <uuid> > d.json

NEW_PRESET must be the preset object re-serialized to a STRING

NEW_PRESET必须是preset对象重新序列化为字符串后的结果

jq -n --slurpfile d d.json --arg preset "$NEW_PRESET" '{ name: $d[0].name, viewType: ($d[0].viewType // "explore"), status: "active", currentRevision: $d[0].revisionNumber, preset: $preset }' | groundcover dashboards update <uuid> --body-file /dev/stdin
undefined
jq -n --slurpfile d d.json --arg preset "$NEW_PRESET" '{ name: $d[0].name, viewType: ($d[0].viewType // "explore"), status: "active", currentRevision: $d[0].revisionNumber, preset: $preset }' | groundcover dashboards update <uuid> --body-file /dev/stdin
undefined

Validation rules (the API only says
Dashboard validation failed
)

验证规则(API仅返回
Dashboard validation failed

The update endpoint returns an opaque
400 {"message":"Dashboard validation failed"}
— no field, no offending widget. When you hit it, bisect: deploy a minimal preset (one section / one panel) and add pieces back until it breaks. Two non-obvious rules that each cost real bisecting time:
  1. Stat widgets (
    visualizationConfig.type:"stat"
    ) validate ONLY as flat top-level
    layout
    entries — NEVER inside a section's
    children[]
    .
    Time-series widgets work in both. To build an overview stat row, place the stat panels as flat top-level items (with a
    text
    header widget above them) and keep time-series panels in sections.
  2. Section
    color
    accepts only a fixed palette.
    Verified valid:
    gray
    ,
    purple
    ,
    teal
    . Verified rejected:
    green
    ,
    blue
    ,
    red
    ,
    yellow
    ,
    orange
    .
更新端点返回不透明
400 {"message":"Dashboard validation failed"}
——不指明具体字段或有问题的widget。遇到该错误时,请二分排查:部署最小化preset(一个分组/一个面板),然后逐步添加内容直到出错。以下两个非直观规则均需花费大量时间排查:
  1. Stat类型widget(
    visualizationConfig.type:"stat"
    )仅能作为顶层
    layout
    条目——绝不能放在分组的
    children[]
    中。
    Time-series类型widget在两种场景下均可使用。若要构建概览统计行,请将stat面板作为顶层条目(上方放置
    text
    标题widget),将time-series面板放在分组中。
  2. 分组的
    color
    仅接受固定调色板。
    已验证有效的值:
    gray
    purple
    teal
    。已验证无效的值:
    green
    blue
    red
    yellow
    orange

Stat panels read "No Results" on lagging metrics → wrap in
last_over_time

滞后指标导致Stat面板显示“No Results” → 使用
last_over_time
包裹

Stat panels evaluate as an instant query at "now". A metric that lags real-time leaves that instant window empty → the panel shows "No Results" even though it graphs fine in a time-series panel (which range-queries the whole window). Metrics pulled from polled integrations (e.g. cloud-provider integrations like CloudWatch) commonly lag minutes behind real-time, so stat panels built directly on them come up blank.
Fix: wrap the selector in
last_over_time(<selector>[30m])
inside the aggregation so the instant evaluation reaches back past the staleness:
promql
max(last_over_time(my_metric{...}[30m]))   # NOT  max(my_metric{...})
The
*_over_time
wrappers render fine in stat panels (e.g. a working
100 * avg_over_time((...)[7d:5m])
uptime stat). In-cluster metrics scraped in real-time (~1s fresh) don't need it.
Stat面板会在“当前时间”执行即时查询。若指标存在实时滞后,该即时窗口会为空→面板显示**“No Results”**,即使该指标在time-series面板中可正常绘制(time-series面板会查询整个时间范围)。从轮询集成(如CloudWatch等云提供商集成)获取的指标通常会滞后数分钟,因此直接基于这些指标构建的Stat面板会显示空白。
修复方法:将选择器包裹在
last_over_time(<selector>[30m])
中,并放在聚合函数内部,以便即时查询能覆盖滞后时间:
promql
max(last_over_time(my_metric{...}[30m]))   # 不要使用max(my_metric{...})
*_over_time
包装器在Stat面板中可正常显示(例如正常工作的
100 * avg_over_time((...)[7d:5m])
可用率统计)。实时采集的集群内指标(约1秒延迟)无需此处理。

Verifying a dashboard without the UI

无需UI即可验证仪表板

If the headless browser hits Groundcover's SSO wall (no session), you can't screenshot-verify. Verify via the API instead:
  • Every query returns data: pull the deployed preset, substitute concrete values into each
    expr
    (a
    $var
    → a real label value; a wildcard var → a
    =~".+"
    regex), and run it through
    metrics query
    as a range query. Use a window ≥ the metric's emit cadence (sparse integration metrics may only emit every few minutes).
  • Geometry: assert no two layout rects overlap and every
    x+w ≤ 24
    (top-level entries and each section's
    children
    ).
  • Caveat: the CLI can't reliably run instant queries (see Common issues), so you can't reproduce the stat-panel instant path from the CLI — reason from staleness + range data.
若无头浏览器遇到Groundcover的SSO墙(无会话),无法通过截图验证。可通过API验证:
  • 每个查询均返回数据: 获取已部署的preset,将具体值代入每个
    expr
    $var
    替换为实际标签值;通配符变量替换为
    =~".+"
    正则),然后通过
    metrics query
    执行范围查询。时间窗口需≥指标的发送频率(稀疏的集成指标可能仅每隔数分钟发送一次)。
  • 几何布局: 确保没有两个布局矩形重叠,且所有
    x+w ≤ 24
    (顶层条目和每个分组的
    children
    均需满足)。
  • 注意: CLI无法可靠执行即时查询(见常见问题),因此无法从CLI复现Stat面板的即时查询路径——需根据滞后情况和范围数据进行推断。

Polled-integration metrics (e.g. CloudWatch): label-scheme gotchas

轮询集成指标(如CloudWatch):标签方案陷阱

Metrics ingested from a polled cloud integration differ from in-cluster exporter metrics in ways that bite when building dashboards:
  • They lag real-time (see the stat-panel note above) — minutes, not seconds.
  • They're often multiplied by a
    stat
    dimension
    (
    stat=average/maximum/minimum
    ) — every series appears 3×. Always filter to the one you want (e.g.
    stat="average"
    ) or aggregations double/triple-count.
  • The env/scope label is integration-named, not a clean
    env
    . If the same resource is also covered by an in-cluster exporter, the two families carry different label keys for the same concept (e.g.
    instance_id
    vs
    instanceid
    ). Use a separate template variable per label scheme rather than forcing one variable to span both.
从轮询云集成摄入的指标与集群内导出器指标存在差异,构建仪表板时需注意:
  • 存在实时滞后(见Stat面板说明)——数分钟而非数秒。
  • 通常带有
    stat
    维度
    stat=average/maximum/minimum
    )——每个时间序列会出现3次。请始终过滤到所需的维度(如
    stat="average"
    ),否则聚合结果会重复计数。
  • 环境/范围标签为集成特定名称,而非简洁的
    env
    。若同一资源同时被集群内导出器覆盖,两个指标系列的标签键会不同(如
    instance_id
    vs
    instanceid
    )。请为每种标签方案使用单独的模板变量,而非强制使用一个变量覆盖两种场景。

Production observability triage flow

生产环境可观测性排查流程

When asked "why is X broken in prod" / "what's going on with service Y":
  1. Scope the time window. Default to last 1h. Compute
    START
    /
    END
    with
    date -u
    (see recipes).
  2. Logs first.
    groundcover logs search
    with a GCQL filter on
    service_name
    and
    level = "error"
    . Cheap and usually answers it.
  3. Traces if latency / slow.
    groundcover traces search
    filtering on
    duration_ms
    ,
    status_code
    , or
    service_name
    . Grab a few
    trace_id
    s and pivot back to logs via
    trace_id = "..."
    .
  4. Metrics for shape over time.
    groundcover metrics query
    with PromQL — error rate, p99, saturation. Use
    range
    for graphs,
    instant
    for a point check.
  5. K8s if "is it even running".
    groundcover k8s workloads
    for status/restarts;
    groundcover k8s events-search
    for
    Warning
    events (OOMKilled, FailedScheduling, BackOff).
  6. Don't know the field name?
    groundcover search discovery
    ,
    metrics names
    ,
    metrics keys
    ,
    metrics values
    to enumerate.
  7. Need to silence noisy alerts during the incident?
    groundcover monitors list --query …
    groundcover silences create
    .
当被问及“为什么X在生产环境故障” / “服务Y出现了什么问题”时:
  1. 确定时间范围。 默认最近1小时。使用
    date -u
    计算
    START
    /
    END
    (见示例)。
  2. 优先查看日志。 使用
    groundcover logs search
    ,通过GCQL过滤
    service_name
    level = "error"
    。成本低且通常能直接找到问题原因。
  3. 若涉及延迟/慢请求,查看链路追踪。 使用
    groundcover traces search
    过滤
    duration_ms
    status_code
    service_name
    。获取几个
    trace_id
    并通过
    trace_id = "..."
    关联回日志。
  4. 查看指标了解时间趋势。 使用
    groundcover metrics query
    执行PromQL查询——错误率、p99延迟、饱和度。使用
    range
    查询获取图表,使用
    instant
    查询获取单点数据。
  5. 若怀疑“服务是否运行”,查看K8s状态。 使用
    groundcover k8s workloads
    查看状态/重启次数;使用
    groundcover k8s events-search
    查看
    Warning
    事件(OOMKilled、FailedScheduling、BackOff)。
  6. 不知道字段名称? 使用
    groundcover search discovery
    metrics names
    metrics keys
    metrics values
    枚举字段。
  7. 事件期间需要静默嘈杂的告警? 使用
    groundcover monitors list --query …
    查找监控器 → 使用
    groundcover silences create
    创建静默规则。

When unsure which command exists

不确定命令是否存在时

  1. Check the resource list above.
  2. groundcover --help
    and
    groundcover <resource> --help
    .
  3. For raw:
    groundcover raw list
    , then
    groundcover raw list <group>
    .
  1. 查看上文的资源列表。
  2. 执行
    groundcover --help
    groundcover <resource> --help
  3. 对于原始命令:执行
    groundcover raw list
    ,然后执行
    groundcover raw list <group>

Common issues

常见问题

401 Unauthorized
, token-prefix/length errors, or empty results

401 Unauthorized
、令牌前缀/长度错误或空结果

  • Invalid token prefix
    /
    Invalid token length
    → the key isn't a
    gcsa_
    service-account key (see Auth). A
    gcik_
    ingestion key is the common mistake.
  • 401
    with a valid-looking
    gcsa_
    key → the key is for a different tenant. For other tenants also set
    GROUNDCOVER_TENANT_UUID
    and (if applicable)
    GROUNDCOVER_BASE_URL
    .
  • Otherwise verify the env var is actually exported in the current shell.
  • Invalid token prefix
    /
    Invalid token length
    → 密钥不是
    gcsa_
    服务账号密钥(见认证章节)。常见错误是使用
    gcik_
    数据摄入密钥。
  • 格式正确的
    gcsa_
    密钥返回
    401
    → 密钥所属的租户与当前查询的租户不一致。访问其他租户时还需设置
    GROUNDCOVER_TENANT_UUID
    和(若适用)
    GROUNDCOVER_BASE_URL
  • 否则请确认环境变量已在当前shell中正确导出。

SDK command rejects the body

SDK命令拒绝请求体

Body shape drifted from the SDK contract. Fetch an existing resource with
get <id>
and use its shape as a template.
请求体格式与SDK契约不一致。使用
get <id>
获取现有资源,并以其格式为模板。

Empty results from
logs search
/
traces search
/
events-search

logs search
/
traces search
/
events-search
返回空结果

Almost always one of:
  • Time window is wrong / too narrow.
    start
    must be before
    end
    , both RFC 3339 UTC.
  • GCQL field name doesn't exist in this tenant. Use
    search discovery
    /
    search keys
    to enumerate.
  • Query string isn't escaped — inside JSON,
    service_name = "api"
    becomes
    "service_name = \"api\""
    .
几乎总是以下原因之一:
  • 时间范围错误/过窄。
    start
    必须早于
    end
    ,且均为RFC 3339 UTC格式。
  • GCQL字段名称在当前租户中不存在。使用
    search discovery
    /
    search keys
    枚举字段。
  • 查询字符串未转义——在JSON中,
    service_name = "api"
    需写为
    "service_name = \"api\""

metrics query
returns no data but the metric exists

metrics query
返回空数据但指标存在

  • Field names are capitalized (
    Promql
    ,
    Start
    ,
    End
    ,
    Step
    ,
    QueryType
    ) — lowercase is silently accepted by JSON but ignored.
  • Step
    is a string (
    "60"
    ), not a number.
  • For instant queries set
    "QueryType": "instant"
    and omit
    Step
    . Observed caveat: in testing the CLI
    metrics query
    has returned
    400 metricsQueryBadRequest
    for instant bodies (every shape tried —
    Start
    ==
    End
    ,
    Time
    , with/without
    QueryType
    ), even on fresh metrics. When you need a single current value, fall back to a range query (
    QueryType:"range"
    +
    Step
    ) over a short window and take the last point. (Genuine instant PromQL is also reachable via
    raw prometheus api query
    , but that passthrough may target a different store than
    metrics query
    .)
  • Parsing the response: results live at
    .data.result // .result
    ; each
    values
    entry is
    [unix_ts, "stringvalue"]
    . Sum a range with
    [.values[][1]] | map(tonumber) | add
    ; format the timestamp with
    strftime
    .
  • 字段名称首字母大写
    Promql
    Start
    End
    Step
    QueryType
    )——小写会被JSON接受但被忽略。
  • Step
    为字符串(
    "60"
    ),而非数字。
  • 即时查询需设置
    "QueryType": "instant"
    并省略
    Step
    已知问题: 测试中发现CLI的
    metrics query
    对即时请求体返回
    400 metricsQueryBadRequest
    (尝试过所有格式——
    Start
    ==
    End
    Time
    、带/不带
    QueryType
    ),即使是最新指标。若需要当前单点值,请回退到范围查询(
    QueryType:"range"
    +
    Step
    ),查询短时间窗口并取最后一个数据点。(真正的即时PromQL也可通过
    raw prometheus api query
    执行,但该透传可能指向与
    metrics query
    不同的存储。)
  • 响应解析:结果位于
    .data.result // .result
    ;每个
    values
    条目格式为
    [unix_ts, "stringvalue"]
    。求和范围数据可使用
    [.values[][1]] | map(tonumber) | add
    ;使用
    strftime
    格式化时间戳。

logs search
times out (
context deadline exceeded
)

logs search
超时(
context deadline exceeded

Chatty, high-volume workloads flood logs, so broad or single-pod queries over even a ~60–90s window can exceed the deadline. Fixes:
  • Always include a discriminating term (a phrase +
    container
    /
    pod
    ), not just a pod by itself.
  • Narrow the window and raise
    --timeout
    (e.g.
    --timeout 120s
    ).
  • Avoid large
    OR
    chains in one query — run them separately.
  • For "how often / over time" questions prefer
    metrics query
    over scraping logs.
高流量的嘈杂工作负载会产生大量日志,因此即使是约60–90秒的宽范围或单Pod查询也可能超过超时时间。修复方法:
  • 始终包含区分性条件(短语 +
    container
    /
    pod
    ),而非仅指定Pod。
  • 缩小时间范围并增加
    --timeout
    (如
    --timeout 120s
    )。
  • 避免在单个查询中使用大型
    OR
    链——分开执行查询。
  • 对于“频率/时间趋势”类问题,优先使用**
    metrics query
    **而非日志查询。

monitors
editing & search

monitors
编辑与搜索

  • monitors get
    /
    update
    speak YAML (even with
    --raw
    ). Workflow:
    monitors get <uuid> > mon.yaml
    , edit,
    monitors update <uuid> --body-file mon.yaml
    . A successful update returns
    {"status":202}
    (applied asynchronously — re-
    get
    to confirm).
  • Monitor model lives under
    model.queries[].sqlPipeline.filters.conditions[]
    — each condition is
    {key, origin: root, type, filters:[{op, value}]}
    ; freetext uses
    type: freetext
    +
    op: phrase_search
    . Other knobs:
    instantRollup
    (e.g.
    1 minute
    ),
    thresholds[]
    ,
    evaluationInterval.{interval, pendingFor}
    .
  • monitors list --query 'monitor_name ~ "..."'
    → 400
    regexp requires a string column type
    . Use
    monitor_name = "exact"
    , or list all and filter with
    jq
    .
  • monitors get
    /
    update
    使用YAML格式(即使使用
    --raw
    )。工作流:
    monitors get <uuid> > mon.yaml
    ,编辑文件,
    monitors update <uuid> --body-file mon.yaml
    。更新成功后返回
    {"status":202}
    (异步应用——需重新执行
    get
    确认)。
  • 监控器模型位于
    model.queries[].sqlPipeline.filters.conditions[]
    ——每个条件格式为
    {key, origin: root, type, filters:[{op, value}]}
    ;自由文本使用
    type: freetext
    +
    op: phrase_search
    。其他配置项:
    instantRollup
    (如
    1 minute
    )、
    thresholds[]
    evaluationInterval.{interval, pendingFor}
  • monitors list --query 'monitor_name ~ "..."'
    → 返回400错误
    regexp requires a string column type
    。请使用
    monitor_name = "exact"
    ,或列出所有监控器并使用
    jq
    过滤。

Raw command "not found" inside a group

分组内的原始命令“未找到”

The endpoint isn't in the captured HAR. Use the SDK form if one exists, or regenerate — regeneration lives in the
groundcover-cli
repo (
go run ./scripts/generate-commands.go <har>
).
该端点未被捕获到HAR中。若存在SDK命令则使用SDK方式,或重新生成——生成逻辑位于
groundcover-cli
仓库(
go run ./scripts/generate-commands.go <har>
)。