Lolly
啟動應用程式

Metrics and alerts

備註: Lolly Work

This guide covers the optional organisation service. Configuration, console and API paths refer to your Work instance. The packaged console keeps its release-matched copy of this guide.

Lolly Work exports Prometheus metrics on GET /metrics. This page lists every metric, the alerts the Helm chart can install and the Grafana dashboard that comes with them. The alerts and the dashboard are for a platform team that already runs Prometheus and Grafana, and both are off by default. Without them, Work still raises its own console alerts for blob offload and credential expiry (keep Lolly Work running).

Scraping

/metrics needs a bearer token, LW_METRICS_TOKEN, from anywhere except an evaluation instance's own loopback address:

With the Helm chart, set secrets.metricsToken (or an LW_METRICS_TOKEN key in your existingSecret) and turn on the ServiceMonitor. It reads the token from the same Secret:

metrics:
  serviceMonitor:
    enabled: true
    interval: 30s
    labels:
      release: kube-prometheus-stack   # what your Prometheus's serviceMonitorSelector matches

Your own scrape job works as well. Give it the bearer token and see selecting the series for the alerts.

Metric reference

Counters count from process start, so a restart sets them back to zero; rate() and increase() allow for that. A counter, and each of its label combinations, appears with its first event, so Prometheus can measure an increase only from the second event on. Gauges are read when Prometheus scrapes.

Requests and the process

MetricTypeLabelsMeaning
lw_http_requests_totalcounterroute, statusRequests answered. route is the matched route pattern, such as /api/v1/projects/:id, or unmatched, csrf-blocked or ratelimited:<surface>. status is the class, 2xx to 5xx. Kubelet probes and scrapes count too, under /healthz, /readyz and /metrics
lw_rate_limited_totalcountersurfaceRequests the per-address limiter refused, by surface: auth, telemetry, link or automation
lw_rate_limit_bucketsgaugeClient addresses the limiter tracks in memory. When the count reaches rateLimit.maxBuckets, the limiter drops the oldest tenth
lw_origin_bytes_totalcounterclassResponse body bytes after compression, by delivery class: public-immutable, public-revalidate, public-bounded, link, instance-private or person-private. HEAD, 204 and 304 answers count nothing. A CDN can serve only the public classes
lw_process_uptime_secondsgaugeSeconds since the Work process started
lw_process_resident_memory_bytesgaugeResident memory of the Work process
lw_org_config_poll_totalcounterConfiguration polls from Lolly apps that Work answered (200 and 304): how often the fleet checks in
lw_org_config_poll_errors_totalcounterConfiguration polls Work could not answer

Audit and SIEM

MetricTypeLabelsMeaning
lw_audit_chain_intactgauge1 when the audit hash chain verifies end to end, 0 when it is broken. Work verifies the chain at most once every 10 seconds, however often Prometheus scrapes
lw_siem_laggaugeAudit events the SIEM receiver has not confirmed. Present only when siem.url is set (SIEM forwarding)

Files

MetricTypeLabelsMeaning
lw_blob_stored_bytesgaugedriverBytes held in the blob store, for pg and memory. The s3 driver reports no total
lw_blob_read_bytes_totalcounterdriverBytes read from the blob store since the process started

Catalog sources

One series per source and metric.

MetricTypeLabelsMeaning
lw_provider_enabledgaugeprovider, kind1 enabled, 0 disabled
lw_provider_assetsgaugeproviderAssets from the last sync
lw_provider_last_errorgaugeprovider1 when the last sync recorded an error
lw_provider_credential_expiry_daysgaugeproviderDays until the expiry date entered with the credential, negative once that date has passed. Present only where an expiry was entered

Notifications, flags and OpenTelemetry

MetricTypeLabelsMeaning
lw_notify_totalcounterchannel, outcomeNotification deliveries, by smtp or webhook and sent or failed (notifications)
lw_ofrep_requests_totalcounterflagFlag evaluations sent to your flag service over OFREP
lw_ofrep_evaluation_errors_totalcounterflag, reasonEvaluations that fell back to the value Work governs. reason is one of timeout, network, unauthorized, not_found, rate_limited, targeting_key_missing, invalid_request, server, status, bad_response or type_mismatch
lw_otlp_exported_totalcountersignalSpans, data points or log records the OTLP receiver accepted. Present only while OpenTelemetry export is on
lw_otlp_dropped_totalcountersignal, reasonItems lost to a full queue (queue_full) or a failed export (export_failed). Present only while OpenTelemetry export is on

The render worker exports no metrics. Its health shows in its readiness probe and in the status of server renders, the /api/v1/render and /render/:spec routes of lw_http_requests_total.

Suggested alerts

The chart can install twelve alerts as a Prometheus Operator PrometheusRule, next to the ServiceMonitor:

metrics:
  serviceMonitor:
    enabled: true
  prometheusRule:
    enabled: true
    labels:
      release: kube-prometheus-stack   # what your Prometheus's ruleSelector matches
AlertSeverityFires whenReads
LollyWorkAuditChainBrokencriticalThe audit chain has not verified for a minutelw_audit_chain_intact
LollyWorkNotReadycriticalEvery /readyz check in two minutes answered 503, for five minuteslw_http_requests_total
LollyWorkMetricsAbsentwarningPrometheus has had no Work series for ten minuteslw_process_uptime_seconds
LollyWorkHighErrorRatewarningOver 5% of requests answered 5xx for ten minutes, leaving out health checks, scrapes and renders, at three requests a minute or morelw_http_requests_total
LollyWorkRenderFailureswarningOver 10% of server renders (/api/v1/render and /render/:spec) answered 5xx in 15 minutes, with five renders or morelw_http_requests_total
LollyWorkSiemLagGrowingwarningThe SIEM backlog has not reached zero and has kept growing for 30 minuteslw_siem_lag
LollyWorkBlobStoreNearOffloadwarningPostgreSQL (driver="pg") has held over 9 GiB of files for an hourlw_blob_stored_bytes
LollyWorkBlobReadsNearOffloadwarningAt the last day's rate, a 30-day month would read over 90 GiB of files from PostgreSQL, for an hourlw_blob_read_bytes_total
LollyWorkProviderSyncFailingwarningAn enabled catalog source's last sync has recorded an error for an hourlw_provider_last_error, lw_provider_enabled
LollyWorkProviderCredentialExpiringwarning14 days or fewer remain before a source credential's expiry date, for an hourlw_provider_credential_expiry_days
LollyWorkNotificationsFailingwarningEvery delivery on a channel in the last hour failed, for 15 minuteslw_notify_total
LollyWorkRateLimitBucketsHighwarningThe limiter has tracked over 80% of rateLimit.maxBuckets addresses for 15 minuteslw_rate_limit_buckets

Each alert carries a severity label, a summary and a description that says what to check first. Some limits to know:

Alert options

ValueEffect
metrics.prometheusRule.labelsLabels on the PrometheusRule, for your Prometheus's ruleSelector
metrics.prometheusRule.namespaceWhere the PrometheusRule goes, if not the release namespace
metrics.prometheusRule.alertLabelsLabels added to every alert, such as a team or a routing key
metrics.prometheusRule.disabledAlert names to leave out. An unknown name stops the render
metrics.prometheusRule.seriesSelectorLabel matchers for this release's series, described next

Selecting the series

Every expression selects this release's series with job and namespace. By default they are the labels the chart's ServiceMonitor produces: job is the release's Service name (lolly-work for a release called lolly-work) and namespace is the release namespace. If your own scrape job collects Work's metrics, set metrics.prometheusRule.seriesSelector to matchers for its series, for example job="lolly-work-prod".

Checking the rules

The chart's tests render the rules and run promtool check rules on them, along with the unit tests in deploy/helm/files/prometheus-rules.test.yaml, which cover every alert. To check a rendered rule yourself, take spec.groups from the rendered PrometheusRule into a file of its own and run promtool check rules on that file.

Dashboard

The chart can also install a Grafana dashboard as a ConfigMap:

metrics:
  grafanaDashboard:
    enabled: true

The ConfigMap carries the label grafana_dashboard: "1", which the Grafana sidecar of kube-prometheus-stack watches by default. Change metrics.grafanaDashboard.labels for another sidecar, set metrics.grafanaDashboard.namespace if the sidecar watches one namespace only, and use metrics.grafanaDashboard.annotations for a folder (grafana_folder: Lolly). To import by hand, use deploy/helm/dashboards/lolly-work.json.

The dashboard has three variables, the Prometheus data source, the namespace and the job, and five rows:

Qualification

On 2026-10-27, Work 1.1.2 ran on K3s 1.35 with kube-prometheus-stack 92.14.0 and Grafana 13.2.4. The ServiceMonitor scrape was up with the bearer token, Prometheus loaded all twelve rules with no errors, every metric the rules read had a live series (the failure-only series aside, which have their paired success series), and the sidecar loaded the dashboard with data in each row.