A dashboard is a saved grid of panels in a project. Each panel charts or lists logs, traces or
metrics, and one dashboard can mix all three. Dashboards are resources like datasets: each has an
ID, a dsh_ slug, a resource name, tags and its own URL, and its last 50 saved versions are kept.
A dashboard holds queries, not data. Every panel is read as the person viewing the dashboard, with their own access, each time the dashboard loads or refreshes.
Build a dashboard
- In the console, open Observability > Dashboards and select Create dashboard.
- Give it a name, and optionally a description and tags.
- Start from a blank dashboard or a template: Service overview (request rate, error rate and p95 latency by service from span metrics, with recent error logs) or Logs overview (log volume by level, the top services and routes, and errors).
- The new dashboard opens in edit mode. Select Add panel, choose a signal, write the query, and pick a visualization. The preview runs as you type.
- Drag panels to move them and drag a panel’s corner to resize it, then select Save.
For clusters that run EZGH Obs Agent, the Kubernetes section’s pages are built in; Duplicate as dashboard copies one into a dashboard of your own to edit.
You can also start from what you’re looking at: on the Logs, Traces and Metrics pages, Add to dashboard turns the current query into a panel on a new or existing dashboard. On a dashboard, a panel’s Explore opens its query in the matching page, with the same time range and environment.
Edits stay unsaved until you select Save. If someone else saved the dashboard since you opened it, saving asks whether to reload their version or overwrite it with yours.
Panels
All of a panel’s queries (up to 4) use one signal. Logs and traces panels use the same filter
syntax as the Logs and Traces pages, such as level:error service:checkout or
attr.http.route:/cart.
| Signal | A chart shows | A list shows |
|---|---|---|
| Metrics | A PromQL query, over the range or at one instant, with a legend template like {{service_name}} |
|
| Logs | A measure of the matching records, optionally grouped by up to 2 fields | Matching records, newest first |
| Traces | A measure of the matching spans, optionally grouped by up to 2 fields | Matching spans; the latency heatmap |
Logs and traces measures are count, rate (per second), countDistinct (approximate), sum,
avg, min, max, p50, p90, p95 and p99. Traces add errors and errorRate. A
measure of a field reads a numeric attribute, or a span’s duration; values that aren’t numbers
are skipped. Group by severityText, serviceName, scopeName or application (and name,
statusCode or kind for spans), or by an attribute such as attr.http.route. A panel shows up
to 50 groups (10 by default); the rest add up into one other group.
| Visualization | Shows | Signals |
|---|---|---|
| Time series | Lines, area or bars over time, optionally stacked | All |
| Stat | One number, with an optional sparkline and the change from the previous period | All |
| Top list | Groups ranked as bars | All |
| Table | Groups and their values; for metrics, an instant query’s series | All |
| Heatmap | Latency distribution over time | Traces; metrics histograms |
| Logs | The log stream; a row opens the record | Logs |
| Traces | The span list; a row opens the trace | Traces |
| Text | Markdown, with links to http and https URLs only |
None |
Panels can set a unit, decimals, minimum and maximum, a legend (list, table or hidden), and, for stat and table panels, thresholds that color values as success, warning or danger. A panel can also use its own relative time range, such as the last hour on a 7-day dashboard.
- Value mappings (stat and table panels) show numbers as words: up to 20 entries, each a value,
the text to show (up to 64 characters) and optionally a status (success, warning or danger) that
colors it. A pod phase of
2can show as a green Running. A mapped value takes the mapping’s status instead of the thresholds’. In a table, mappings apply to the first value column. - No data text (every panel) replaces “No data in this range” with your own text, up to 200 characters, such as how to start sending what the panel needs.
- Row links (table and top list panels) make each row open a dashboard; see Row links.
A panel that can’t be read shows why, and the rest of the dashboard still loads: a query over its
budget (422) asks you to narrow the time range or filters, as the explorers do, and a panel you
don’t have access to names the action you need.
Variables
Variables are pickers at the top of the dashboard, used in queries as $name. A dashboard has
up to 10.
| Kind | Values |
|---|---|
environment |
The project’s environments (datasets). Every dashboard has $environment. |
application |
The applications your services report |
values |
The values of a metric label, or of a log or span attribute |
custom |
A fixed list you type in |
application, values and custom variables can allow several values and offer All. In
PromQL, several values become a regular expression (a|b) and All becomes .*, so use them
with =~: {service_name=~"$service"}. In logs and traces filters, several values match any of
them.
Logs and traces panels read one environment at a time; with All environments selected they ask you to pick one. Metrics panels can read all environments at once.
A values variable’s match can use other variables, so one variable can depend on another: a
pod variable matched on k8s_pod_cpu_usage{k8s_namespace_name="$namespace"} offers only the
pods in the selected namespace. When a variable changes, those that depend on it keep the values
that are still offered; one left with none goes back to its default, or to All or the first
value when the default isn’t offered. Variables can’t depend on each other in a loop.
PromQL queries can also use $__interval (the panel’s step), $__rate_interval (a range for
rate() suited to the step) and $__range (the time range).
Row links
A table or top list panel can open another dashboard from each row, with that row’s values set as
the other dashboard’s variables. The link carries the time range and the environment, and this
dashboard’s other variables, such as cluster; the row’s values win.
"viz": {
"type": "table",
"rowLink": {
"dashboard": "builtin:kubernetes-pod",
"vars": { "namespace": "{{k8s_namespace_name}}", "pod": "{{k8s_pod_name}}" }
}
}dashboardis a dashboard’sid(a UUID, not itsdsh_slug), orbuiltin:and the ID of one of the Kubernetes section’s pages:builtin:kubernetes-cluster,builtin:kubernetes-node,builtin:kubernetes-workload,builtin:kubernetes-podorbuiltin:kubernetes-agent, which open that page for the row’s cluster, namespace, node, workload or pod. Rows that don’t name a page aren’t links. A dashboard that doesn’t exist or that the viewer can’t see opens as it does from its own URL.varsis required, and sets up to 10 of the target’s variables ({}sets none). Values use the legend’s{{label}}syntax, filled from the row’s labels (metrics) or group values (logs and traces), up to 256 characters. A row missing one of the labels isn’t a link.
Time range and refresh
A dashboard has a default time range, relative (such as the last 6 hours) or absolute, and an auto-refresh interval: off (the default), 30 seconds, or 1, 5 or 15 minutes, or 1 hour. Auto-refresh pauses while the browser tab is hidden. Dragging across a time series chart zooms the whole dashboard to that range.
Share a dashboard
A dashboard’s URL holds what you’re looking at: the time range, the environment and each variable’s values. Copy it from the address bar or with Copy link, and anyone with access to the dashboard opens the same view. Dashboards aren’t shared outside your organization: there are no public links.
Version history
Creating a dashboard saves version 1. Each save that changes its name, description or panels saves the next version; a change of tags alone doesn’t. The last 50 versions are kept, with who saved each one and when, so you can see which queries a dashboard ran at any time.
To restore a version, open the dashboard’s version history and select Restore, or apply the version’s spec from the CLI. Restoring saves it as a new version; history isn’t rewritten.
Manage dashboards as code
A dashboard is a JSON document: the file the CLI applies is the JSON the console shows in View JSON. Keep dashboard files in Git next to the services they describe, and apply them from CI.
{
"name": "Checkout service",
"description": "Requests and errors of the checkout service",
"tags": { "team": "payments" },
"spec": {
"schemaVersion": 1,
"time": { "range": "6h" },
"refresh": "off",
"variables": [
{ "name": "environment", "kind": "environment", "default": "production" },
{
"name": "service",
"kind": "values",
"label": "Service",
"source": { "signal": "metrics", "label": "service_name", "match": "http_server_request_duration_seconds_count" },
"multi": true,
"includeAll": true
}
],
"panels": [
{
"id": "p1",
"title": "Request rate by service",
"grid": { "x": 0, "y": 0, "w": 12, "h": 8 },
"viz": { "type": "timeseries", "style": "lines", "unit": "reqps", "legend": "table" },
"signal": "metrics",
"queries": [
{
"ref": "A",
"promql": "sum by (service_name) (rate(http_server_request_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
"legend": "{{service_name}}"
}
]
},
{
"id": "p2",
"title": "Errors by route",
"grid": { "x": 12, "y": 0, "w": 12, "h": 8 },
"viz": { "type": "timeseries", "style": "bars", "stack": true },
"signal": "logs",
"queries": [
{
"ref": "A",
"filter": "level:error service:$service",
"aggregate": { "fn": "count" },
"groupBy": [{ "field": "attr.http.route" }],
"limit": 10
}
]
},
{
"id": "p3",
"title": "Recent errors",
"grid": { "x": 0, "y": 8, "w": 24, "h": 10 },
"viz": { "type": "logs", "columns": ["time", "level", "service", "body"] },
"signal": "logs",
"queries": [{ "ref": "A", "filter": "level:error service:$service" }]
}
]
}
}name(1 to 100 characters) andspecare required;description(up to 1,000 characters) andtags(an object of strings) are optional.- In
spec, the grid has 24 columns:xandwcount columns, andyandhcount 30-pixel rows. Panelids are 1 to 16 lowercase letters and digits, unique in the dashboard; queryrefs areAtoD. - The newer optional fields
viz.mappings,viz.noDataandviz.rowLinkkeepschemaVersion1. Consoles from before they existed ignore them. - Unknown fields are refused with
400naming the field. Queries aren’t checked when you save: a query with a mistake fails when its panel runs.
ezgh obs dashboards apply -f checkout.json --project web
ezgh obs dashboards list --project web
ezgh obs dashboards get "Checkout service" -o json > checkout.json
ezgh obs dashboards versions "Checkout service"
ezgh obs dashboards versions get "Checkout service" 3 -o json
ezgh obs dashboards delete "Checkout service"apply finds the dashboard by the file’s id or slug, else by its name if exactly one
dashboard has it, and updates it; when there’s none, it creates one. An update replaces the spec
whole and sets the name, and the description and tags when the file has them. Applying a file
that changes nothing saves no version. The output of get -o json can be applied as it is: its
other fields are ignored. create -f always creates a new dashboard. -f - reads the file from
stdin. See ezgh obs dashboards.
CreateDashboard creates a dashboard, with an
Idempotency-Key. UpdateDashboard takes any of name,
description, tags and spec (replaced whole). Send If-Match with the ETag from
GetDashboard so a change saved in the meantime answers 412
instead of being overwritten. ListDashboards
leaves specs out, and filters by name prefix (q) and tag (tag=key or tag=key=value).
ListDashboardVersions and
GetDashboardVersion read the history, and
DeleteDashboard deletes a dashboard. Logs and traces chart
panels read AggregateLogs and
AggregateSpans.
An update or delete sent with an ETag that’s no longer current is refused with
412 precondition_failed, and nothing changes: someone saved the dashboard after you read it.
Read it again, or run apply again to apply your file over the current version.
Access
| Action | Allows |
|---|---|
obs.dashboards.list |
Listing a project’s dashboards |
obs.dashboards.get |
Viewing a dashboard and its saved versions |
obs.dashboards.create |
Creating dashboards |
obs.dashboards.update |
Changing dashboards, and restoring a version |
obs.dashboards.delete |
Deleting dashboards |
ObsFullAccess includes all five. ObsReadOnlyAccess and ReadOnlyAccess include list and
get.
A dashboard grants nothing. Each panel is read with the viewer’s own obs.logs.search,
obs.traces.search or obs.metrics.query on the environment’s dataset. ObsFullAccess,
ObsReadOnlyAccess and ReadOnlyAccess all include them, so read-only users see the data. With a
custom policy that leaves one out, the panels that need it say which action is missing.
Limits
| Limit | Value |
|---|---|
| Dashboards per project | 200 (quota obs.dashboards.perProject, adjustable up to 1,000) |
| Panels per dashboard | 50 |
| Queries per panel | 4 |
| Variables per dashboard | 10 |
| Spec size | 256 KiB of JSON |
| Saved versions kept | The last 50 |
| Tags | 50 per dashboard: keys 1 to 128 characters, values up to 256 |
Creating a dashboard over the quota is 409 quota_exceeded;
ezgh quotas get obs.dashboards.perProject --scope-key <project> shows the limit and usage. See
Service quotas.
Panels are reads like any other: the search limits and metrics query limits apply to each one. Long time ranges over busy datasets can be over a panel’s budget; give that panel a shorter range of its own.
Trails
Creating, changing and deleting dashboards are recorded in Trails as management events
(CreateDashboard, UpdateDashboard, DeleteDashboard) with eventSource obs.ezghcloud.com.
The spec is recorded as a hidden value, because its queries can contain your data; the version
history keeps it. Viewing a dashboard and reading its panels aren’t recorded.