google-agents-cli-scaffold
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
grafana
Observability visualization with Grafana and LGTM stack. Dashboard design, panel configuration, alerting, variables/templating, and data sources. USE WHEN: Creating Grafana dashboards, configuring panels and visualizations, writing LogQL/TraceQL queries, setting up Grafana data sources, configur...
Full skill instructions
The LGTM stack provides a complete observability solution with comprehensive visualization and dashboard capabilities:
This skill covers setup, configuration, dashboard creation, panel design, querying, alerting, templating, and production observability best practices.
Horizontally scalable log aggregation inspired by Prometheus
Scalable distributed tracing backend
Horizontally scalable long-term Prometheus storage
| Panel Type | Use Case | Best For |
|---|---|---|
| Time Series / Graph | Trends over time | Request rates, latency, resource usage |
| Stat | Single metric value | Error rates, current values, percentage |
| Gauge | Progress toward limit | CPU usage, memory, disk space |
| Bar Gauge | Comparative values | Top N items, distribution |
| Table | Structured data | Service lists, error details, resource inventory |
| Pie Chart | Proportions | Traffic distribution, error breakdown |
| Heatmap | Distribution over time | Latency percentiles, request patterns |
| Logs | Log streams | Error investigation, debugging |
| Traces | Distributed tracing | Performance analysis, dependency mapping |
{{label}} format for dynamic legend namesDashboard variables enable reusable, dynamic dashboards that work across environments, services, and time ranges.
| Type | Purpose | Example |
|---|---|---|
| Query | Populate from data source | Namespaces, services, pods |
| Custom | Static list of options | Environments (prod/staging/dev) |
| Interval | Time interval selection | Auto-adjusted query intervals |
| Datasource | Switch between data sources | Multiple Prometheus instances |
| Constant | Hidden values for queries | Cluster name, region |
| Text box | Free-form input | Custom filters |
{
"templating": {
"list": [
{
"name": "datasource",
"type": "datasource",
"query": "prometheus",
"description": "Select Prometheus data source"
},
{
"name": "namespace",
"type": "query",
"datasource": "${datasource}",
"query": "label_values(kube_pod_info, namespace)",
"multi": true,
"includeAll": true,
"description": "Kubernetes namespace filter"
},
{
"name": "app",
"type": "query",
"datasource": "${datasource}",
"query": "label_values(kube_pod_info{namespace=~\"$namespace\"}, app)",
"multi": true,
"includeAll": true,
"description": "Application filter (depends on namespace)"
},
{
"name": "interval",
"type": "interval",
"auto": true,
"auto_count": 30,
"auto_min": "10s",
"options": ["1m", "5m", "15m", "30m", "1h", "6h", "12h", "1d"],
"description": "Query resolution interval"
},
{
"name": "environment",
"type": "custom",
"options": [
{ "text": "Production", "value": "prod" },
{ "text": "Staging", "value": "staging" },
{ "text": "Development", "value": "dev" }
],
"current": { "text": "Production", "value": "prod" }
}
]
}
}
Variables are referenced with $variable_name or ${variable_name} syntax:
# Simple variable reference
rate(http_requests_total{namespace="$namespace"}[5m])
# Multi-select with regex match
rate(http_requests_total{namespace=~"$namespace"}[5m])
# Variable in legend
rate(http_requests_total{app="$app"}[5m]) by (method)
# Legend format: "{{method}}"
# Using interval variable for adaptive queries
rate(http_requests_total[$__interval])
# Chained variables (app depends on namespace)
rate(http_requests_total{namespace="$namespace", app="$app"}[5m])
Regex filtering:
{
"name": "pod",
"type": "query",
"query": "label_values(kube_pod_info{namespace=\"$namespace\"}, pod)",
"regex": "/^$app-.*/",
"description": "Filter pods by app prefix"
}
All option with custom value:
{
"name": "status",
"type": "custom",
"options": ["200", "404", "500"],
"includeAll": true,
"allValue": ".*",
"description": "HTTP status code filter"
}
Dependent variables (variable chain):
$datasource (datasource type)$cluster (query: depends on datasource)$namespace (query: depends on cluster)$app (query: depends on namespace)$pod (query: depends on app)Annotations display events as vertical markers on time series panels:
{
"annotations": {
"list": [
{
"name": "Deployments",
"datasource": "Prometheus",
"expr": "changes(kube_deployment_spec_replicas{namespace=\"$namespace\"}[5m])",
"tagKeys": "deployment,namespace",
"textFormat": "Deployment: {{deployment}}",
"iconColor": "blue"
},
{
"name": "Alerts",
"datasource": "Loki",
"expr": "{app=\"alertmanager\"} | json | alertname!=\"\"",
"textFormat": "Alert: {{alertname}}",
"iconColor": "red"
}
]
}
}
$__interval for adaptive samplingDashboard provisioning enables GitOps workflows and version-controlled dashboard definitions.
File: /etc/grafana/provisioning/dashboards/dashboards.yaml
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: ''
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
- name: 'application'
orgId: 1
folder: 'Applications'
type: file
disableDeletion: true
editable: false
options:
path: /var/lib/grafana/dashboards/application
- name: 'infrastructure'
orgId: 1
folder: 'Infrastructure'
type: file
options:
path: /var/lib/grafana/dashboards/infrastructure
Complete dashboard JSON with metadata and provisioning:
{
"dashboard": {
"title": "Application Observability - ${app}",
"uid": "app-observability",
"tags": ["observability", "application"],
"timezone": "browser",
"editable": true,
"graphTooltip": 1,
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s",
"templating": { "list": [] },
"panels": [],
"links": []
},
"overwrite": true,
"folderId": null,
"folderUid": null
}
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboards
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
application-dashboard.json: |
{
"dashboard": {
"title": "Application Metrics",
"uid": "app-metrics",
"tags": ["application"],
"panels": []
}
}
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: application-observability
namespace: monitoring
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
json: |
{
"dashboard": {
"title": "Application Observability",
"panels": []
}
}
File: /etc/grafana/provisioning/datasources/loki.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
jsonData:
maxLines: 1000
derivedFields:
- datasourceUid: tempo_uid
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"
editable: false
File: /etc/grafana/provisioning/datasources/tempo.yaml
apiVersion: 1
datasources:
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
uid: tempo_uid
jsonData:
httpMethod: GET
tracesToLogs:
datasourceUid: loki_uid
tags: ["job", "instance", "pod", "namespace"]
mappedTags: [{ key: "service.name", value: "service" }]
spanStartTimeShift: "1h"
spanEndTimeShift: "1h"
tracesToMetrics:
datasourceUid: prometheus_uid
tags: [{ key: "service.name", value: "service" }]
serviceMap:
datasourceUid: prometheus_uid
nodeGraph:
enabled: true
editable: false
File: /etc/grafana/provisioning/datasources/mimir.yaml
apiVersion: 1
datasources:
- name: Mimir
type: prometheus
access: proxy
url: http://mimir:8080/prometheus
uid: prometheus_uid
jsonData:
httpMethod: POST
exemplarTraceIdDestinations:
- datasourceUid: tempo_uid
name: trace_id
prometheusType: Mimir
prometheusVersion: 2.40.0
cacheLevel: "High"
incrementalQuerying: true
incrementalQueryOverlapWindow: 10m
editable: false
Grafana unified alerting supports multi-datasource alerts with flexible evaluation and routing.
File: /etc/grafana/provisioning/alerting/rules.yaml
apiVersion: 1
groups:
- name: application_alerts
interval: 1m
rules:
- uid: error_rate_high
title: High Error Rate
condition: A
data:
- refId: A
queryType: ""
relativeTimeRange:
from: 300
to: 0
datasourceUid: prometheus_uid
model:
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
> 0.05
intervalMs: 1000
maxDataPoints: 43200
noDataState: NoData
execErrState: Error
for: 5m
annotations:
description: 'Error rate is {{ printf "%.2f" $values.A.Value }}% (threshold: 5%)'
summary: Application error rate is above threshold
runbook_url: https://wiki.company.com/runbooks/high-error-rate
labels:
severity: critical
team: platform
isPaused: false
- uid: high_latency
title: High P95 Latency
condition: A
data:
- refId: A
datasourceUid: prometheus_uid
model:
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, endpoint)
) > 2
for: 10m
annotations:
description: "P95 latency is {{ $values.A.Value }}s on endpoint {{ $labels.endpoint }}"
runbook_url: https://wiki.company.com/runbooks/high-latency
labels:
severity: warning
apiVersion: 1
groups:
- name: log_based_alerts
interval: 1m
rules:
- uid: error_spike
title: Error Log Spike
condition: A
data:
- refId: A
queryType: ""
datasourceUid: loki_uid
model:
expr: |
sum(rate({app="api"} | json | level="error" [5m]))
> 10
for: 2m
annotations:
description: "Error log rate is {{ $values.A.Value }} logs/sec"
summary: Spike in error logs detected
labels:
severity: warning
- uid: critical_error_pattern
title: Critical Error Pattern Detected
condition: A
data:
- refId: A
datasourceUid: loki_uid
model:
expr: |
sum(count_over_time({app="api"}
|~ "OutOfMemoryError|StackOverflowError|FatalException" [5m]
)) > 0
for: 0m
annotations:
description: "Critical error pattern found in logs"
labels:
severity: critical
page: true
File: /etc/grafana/provisioning/alerting/contactpoints.yaml
apiVersion: 1
contactPoints:
- orgId: 1
name: slack-critical
receivers:
- uid: slack_critical
type: slack
settings:
url: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
title: "{{ .GroupLabels.alertname }}"
text: |
{{ range .Alerts }}
*Alert:* {{ .Labels.alertname }}
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Severity:* {{ .Labels.severity }}
{{ end }}
disableResolveMessage: false
- orgId: 1
name: pagerduty-oncall
receivers:
- uid: pagerduty_oncall
type: pagerduty
settings:
integrationKey: YOUR_INTEGRATION_KEY
severity: critical
class: infrastructure
- orgId: 1
name: email-team
receivers:
- uid: email_team
type: email
settings:
addresses: [email protected]
singleEmail: true
notificationPolicies:
- orgId: 1
receiver: slack-critical
group_by: ["alertname", "namespace"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: pagerduty-oncall
matchers:
- severity = critical
- page = true
group_wait: 10s
repeat_interval: 1h
continue: true
- receiver: email-team
matchers:
- severity = warning
- team = platform
group_interval: 10m
repeat_interval: 12h
# Simple label matching
{namespace="production", app="api"}
# Regex matching
{app=~"api|web|worker"}
# Not equal
{env!="staging"}
# Multiple conditions
{namespace="production", app="api", level!="debug"}
# Contains
{app="api"} |= "error"
# Does not contain
{app="api"} != "debug"
# Regex match
{app="api"} |~ "error|exception|fatal"
# Case insensitive
{app="api"} |~ "(?i)error"
# Chaining filters
{app="api"} |= "error" != "timeout"
# Parse JSON logs
{app="api"} | json
# Extract specific fields
{app="api"} | json message="msg", level="severity"
# Filter on extracted field
{app="api"} | json | level="error"
# Nested JSON
{app="api"} | json | line_format "{{.response.status}}"
# Parse logfmt (key=value)
{app="api"} | logfmt
# Extract specific fields
{app="api"} | logfmt level, caller, msg
# Filter parsed fields
{app="api"} | logfmt | level="error"
# Extract with pattern
{app="nginx"} | pattern `<ip> - - <_> "<method> <uri> <_>" <status> <_>`
# Filter on extracted values
{app="nginx"} | pattern `<_> <status> <_>` | status >= 400
# Complex pattern
{app="api"} | pattern `level=<level> msg="<msg>" duration=<duration>ms`
# Count log lines over time
count_over_time({app="api"}[5m])
# Rate of logs
rate({app="api"}[5m])
# Errors per second
sum(rate({app="api"} |= "error" [5m])) by (namespace)
# Error ratio
sum(rate({app="api"} |= "error" [5m]))
/
sum(rate({app="api"}[5m]))
# Average duration
avg_over_time({app="api"}
| logfmt
| unwrap duration [5m]) by (endpoint)
# P95 latency
quantile_over_time(0.95, {app="api"}
| regexp `duration=(?P<duration>[0-9.]+)ms`
| unwrap duration [5m]) by (method)
# Top 10 error messages
topk(10,
sum by (msg) (
count_over_time({app="api"}
| json
| level="error" [1h]
)
)
)
# Find traces by service
{ .service.name = "api" }
# HTTP status codes
{ .http.status_code = 500 }
# Combine conditions
{ .service.name = "api" && .http.status_code >= 400 }
# Duration filter
{ duration > 1s }
# Parent-child relationship
{ .service.name = "frontend" }
>> { .service.name = "backend" && .http.status_code = 500 }
# Descendant spans
{ .service.name = "api" }
>>+ { .db.system = "postgresql" && duration > 1s }
# Failed database queries
{ .service.name = "api" }
>> { .db.system = "postgresql" && status = "error" }
{
"dashboard": {
"title": "Application Observability - ${app}",
"tags": ["observability", "application"],
"timezone": "browser",
"editable": true,
"graphTooltip": 1,
"time": {
"from": "now-1h",
"to": "now"
},
"templating": {
"list": [
{
"name": "app",
"type": "query",
"datasource": "Mimir",
"query": "label_values(up, app)",
"current": {
"selected": false,
"text": "api",
"value": "api"
}
},
{
"name": "namespace",
"type": "query",
"datasource": "Mimir",
"query": "label_values(up{app=\"$app\"}, namespace)",
"multi": true,
"includeAll": true
}
]
},
"panels": [
{
"id": 1,
"title": "Request Rate",
"type": "graph",
"datasource": "Mimir",
"targets": [
{
"expr": "sum(rate(http_requests_total{app=\"$app\", namespace=~\"$namespace\"}[$__rate_interval])) by (method, status)",
"legendFormat": "{{method}} - {{status}}"
}
],
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"yaxes": [
{
"format": "reqps",
"label": "Requests/sec"
}
]
},
{
"id": 2,
"title": "P95 Latency",
"type": "graph",
"datasource": "Mimir",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app=\"$app\", namespace=~\"$namespace\"}[$__rate_interval])) by (le, endpoint))",
"legendFormat": "{{endpoint}}"
}
],
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"yaxes": [
{
"format": "s",
"label": "Duration"
}
],
"thresholds": [
{
"value": 1,
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt"
}
]
},
{
"id": 3,
"title": "Error Rate",
"type": "graph",
"datasource": "Mimir",
"targets": [
{
"expr": "sum(rate(http_requests_total{app=\"$app\", namespace=~\"$namespace\", status=~\"5..\"}[$__rate_interval])) / sum(rate(http_requests_total{app=\"$app\", namespace=~\"$namespace\"}[$__rate_interval]))",
"legendFormat": "Error %"
}
],
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"yaxes": [
{
"format": "percentunit",
"max": 1,
"min": 0
}
],
"alert": {
"conditions": [
{
"evaluator": {
"params": [0.01],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": ["A", "5m", "now"]
},
"reducer": {
"type": "avg"
},
"type": "query"
}
],
"frequency": "1m",
"handler": 1,
"name": "Error Rate Alert",
"noDataState": "no_data",
"notifications": []
}
},
{
"id": 4,
"title": "Recent Error Logs",
"type": "logs",
"datasource": "Loki",
"targets": [
{
"expr": "{app=\"$app\", namespace=~\"$namespace\"} | json | level=\"error\"",
"refId": "A"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"dedupStrategy": "none",
"enableLogDetails": true
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
}
}
],
"links": [
{
"title": "Explore Logs",
"url": "/explore?left={\"datasource\":\"Loki\",\"queries\":[{\"expr\":\"{app=\\\"$app\\\",namespace=~\\\"$namespace\\\"}\"}]}",
"type": "link",
"icon": "doc"
},
{
"title": "Explore Traces",
"url": "/explore?left={\"datasource\":\"Tempo\",\"queries\":[{\"query\":\"{resource.service.name=\\\"$app\\\"}\",\"queryType\":\"traceql\"}]}",
"type": "link",
"icon": "gf-traces"
}
]
}
}
File: loki.yaml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_
period: 24h
storage_config:
aws:
s3: s3://us-east-1/my-loki-bucket
s3forcepathstyle: true
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
shared_store: s3
limits_config:
retention_period: 744h # 31 days
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
max_query_series: 500
max_query_lookback: 30d
reject_old_samples: true
reject_old_samples_max_age: 168h
compactor:
working_directory: /loki/compactor
shared_store: s3
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
File: tempo.yaml
server:
http_listen_port: 3200
grpc_listen_port: 9096
distributor:
receivers:
otlp:
protocols:
http:
grpc:
jaeger:
protocols:
thrift_http:
grpc:
ingester:
max_block_duration: 5m
compactor:
compaction:
block_retention: 720h # 30 days
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
region: us-east-1
wal:
path: /var/tempo/wal
metrics_generator:
registry:
external_labels:
source: tempo
cluster: primary
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://mimir:9009/api/v1/push
send_exemplars: true
unwrap instead of parsing when possible$__interval for adaptive samplingauth_enabled: true in Loki/Tempo)High Cardinality: Too many unique label combinations
Query Timeouts: Complex queries on large datasets
Storage Growth: Unbounded retention
Missing Traces: Incomplete trace data
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.
Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.
Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.
Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.
Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.
Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.
Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.
Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Opus.ai: Revolutionize Your Web Experience
Build a no-code AI app in minutes.
An IDE for code migration from legacy to modern frameworks through coding agents.
Automate CGI animation in live-action scenes
Branded artistic QR-code concepts
Launch a website in seconds with AI.
Streamline Your Coding Experience with AI Code Helper
Convert any screenshot or design to clean code.