This is documentation for the next version of Grafana Alloy Documentation. For the latest stable release, go to the latest version.

General availability (GA) Open source

database_observability.postgres

database_observability.postgres connects to a PostgreSQL database and collects observability data from system catalogs and the pg_stat_statements extension. The component collects query details, schema information, explain plans, query samples, and processes PostgreSQL logs. It forwards this data as log entries to Loki receivers and exports targets for Prometheus scraping.

Usage

Alloy
database_observability.postgres "<LABEL>" {
  data_source_name = <DATA_SOURCE_NAME>
  forward_to       = [<LOKI_RECEIVERS>]
}

Arguments

You can use the following arguments with database_observability.postgres:

NameTypeDescriptionDefaultRequired
data_source_namesecretData Source Name for the Postgres server to connect to. Required when no database_instance blocks are defined.no
forward_tolist(LogsReceiver)Where to forward log entries after processing.yes
targetslist(map(string))List of external targets to scrape for Prometheus metrics.no
disable_collectorslist(string)A list of collectors to disable from the default set.no
enable_collectorslist(string)A list of collectors to enable on top of the default set.no
exclude_databaseslist(string)A list of databases to exclude from monitoring.["alloydbadmin", "alloydbmetadata", "azure_maintenance", "azure_sys", "cloudsqladmin", "rdsadmin"]no
exclude_userslist(string)A list of users to exclude from monitoring.["azuresu", "cloudsqladmin", "db-o11y", "rdsadmin"]no
exclude_current_userboolExclude from activity monitoring the user that Alloy uses to connect to the database. The resolved username is automatically added to exclude_users, if not already present.trueno

Refer to the PostgreSQL documentation for more information about the format of the connection strings in data_source_name.

Exports

The following fields are exported and can be referenced by other components:

NameTypeDescription
logs_receiverLogsReceiverReceiver for PostgreSQL logs that processes and exports error metrics. Exported in the single-DSN form only.
logs_receiversmap(LogsReceiver)One logs receiver per [database_instance][database_instance] block, keyed by block label. Empty in the single-DSN form.
targetslist(map(string))Targets that can be used to collect metrics from the component.

The following collectors are configurable:

NameDescriptionEnabled by default
explain_plansCollect query explain plans.yes
table_statsCollect table-level scan statistics.no
index_statsCollect per-index usage statistics.no
query_detailsCollect queries information.yes
query_samplesCollect query samples and wait events information.yes
schema_detailsCollect schemas, tables, and columns from PostgreSQL system catalogs.yes

Blocks

You can use the following blocks with database_observability.postgres:

cloud_provider

The cloud_provider block has no attributes. It contains zero or one of the aws, azure, or gcp blocks. You use the cloud_provider block to provide information related to the cloud provider that hosts the database under observation. This information is appended as labels to the collected metrics. The labels make it easier for you to filter and group your metrics.

aws

The aws block supplies the ARN identifier for the database being monitored.

NameTypeDescriptionDefaultRequired
arnstringThe ARN associated with the database under observation.yes

azure

The azure block supplies the identifying information for the database being monitored.

NameTypeDescriptionDefaultRequired
subscription_idstringThe Subscription ID for your Azure account.yes
resource_groupstringThe Resource Group that holds the database resource.yes
server_namestringThe database server name, for example orders-db for the host orders-db.postgres.database.azure.com.no

gcp

The gcp block supplies the identifying information for the GCP Cloud SQL database being monitored.

NameTypeDescriptionDefaultRequired
connection_namestringThe Cloud SQL instance connection name in the format project:region:instance, for example my-project:us-central1:my-db.yes

database_instance

The database_instance block defines one database server to monitor. Repeat the block to monitor several databases with a single component. The block label must be unique across database_instance blocks and identifies the database in the component’s metrics endpoint path and in the logs_receivers export. Each database_instance block must also point to a distinct server: two blocks that resolve to the same host, port, and database name are rejected.

NameTypeDescriptionDefaultRequired
data_source_namesecretData Source Name for the Postgres server to connect to.yes

Each database_instance block can also contain a [cloud_provider][cloud_provider] block that applies to that database only, and [rule][rule] blocks that relabel the metrics and logs of that database only.

The component always embeds a postgres_exporter for each database_instance block and serves its metrics on the block’s metrics path. Use the [prometheus_exporter][prometheus_exporter] block to configure it. External exporter targets are only supported in the top-level single-DSN form.

When you define database_instance blocks, don’t set the top-level data_source_name, targets, and cloud_provider arguments. They’re mutually exclusive with database_instance blocks. All other arguments and blocks, such as collector settings and prometheus_exporter, apply to every configured database.

The metrics for each database are served on a separate /db/<LABEL>/metrics path under the component’s HTTP endpoint, and the exported targets point to the corresponding path. When you don’t define database_instance blocks, the component serves metrics on its historical /metrics path. The metrics endpoints are served exactly at those paths: requests to any other path under the component’s HTTP endpoint return HTTP 404.

Each database also gets its own logs receiver in the logs_receivers export, keyed by the block label. Forward each database’s PostgreSQL logs to its own receiver so log-derived error metrics are attributed to the right database.

For example:

Alloy
database_observability.postgres "pool" {
  forward_to = [loki.write.logs_service.receiver]

  database_instance "orders" {
    data_source_name = sys.env("ORDERS_DSN")

    cloud_provider {
      aws {
        arn = "orders-rds-db-arn"
      }
    }

    rule {
      target_label = "environment"
      replacement  = "production"
    }
  }

  database_instance "billing" {
    data_source_name = sys.env("BILLING_DSN")

    rule {
      target_label = "environment"
      replacement  = "staging"
    }
  }
}

loki.source.file "orders_db_logs" {
  targets    = local.file_match.orders_db_logs.targets
  forward_to = [database_observability.postgres.pool.logs_receivers["orders"]]
}

In this example, the metrics and logs of the orders database carry environment="production", and those of the billing database carry environment="staging".

The component doesn’t forward the raw PostgreSQL log lines it receives on logs_receivers. It reads them to derive error-message entries, and those derived entries are what the rule blocks relabel. Deriving them requires [logs.enable_error_logs_processing][logs], which is disabled by default.

rule

The rule block relabels the metrics and the logs of the database_instance block that contains it. Use it to tell the data of each monitored database apart, for example by environment, team, or tier.

The rule block defines a relabeling rule applied to the metrics and the logs of a single monitored database. If you define more than one rule block, the rules are applied in top-down order, after the labels the component sets itself, so a rule can read those labels with source_labels.

It takes the same arguments as the rule block used elsewhere in Alloy, in a restricted form: because one rule set applies to two signal paths, it supports the labelling actions only, and it can’t overwrite most of the labels the component sets. Alloy refuses to load a configuration containing a rule that breaks either restriction, so such a rule is never silently ignored.

Two further constraints are specific to a database_instance block:

  • target_label must be a literal label name: Alloy checks it against the reserved labels when it loads the configuration, so it can’t contain a capture group reference.
  • hashmod requires source_labels, and can’t target instance: hashing the empty value gives every database the same result, and hashing into instance buckets several databases onto one value even when the source is present.

Caution

A relabeling rule that computes an empty value removes its target label instead of setting it, and the default replacement of "$1" computes an empty value when a rule has no source_labels. So rule { target_label = "environment" } removes environment rather than setting it, and Alloy rejects it. Always set replacement alongside a target_label.

Alloy can only reject such a rule where the outcome is certain. A rule that reads source_labels depends on the labels present at runtime, so a missing or misspelled source label still removes the target label, except for the reserved labels and instance, which keep their previous value. Only removal is prevented: a rule that computes a non-empty value still overwrites those labels, which is why hashmod can’t target instance at all.

You can use the hashmod, labeldrop, lowercase, replace, and uppercase actions. The rest aren’t supported:

  • drop, dropequal, keep, and keepequal filter rather than label, and filtering means two different things on the two signal paths. Dropping a metrics target stops the database exporting metrics entirely, whereas dropping a log entry discards one line. To filter one database’s logs, use a loki.relabel component on the receiver named in forward_to, with a drop rule that matches that database’s instance or server_id. Every database the component monitors forwards to the same receivers, so the rule has to identify the one it applies to. To filter one database’s metrics, use a discovery.relabel component on the component’s exported targets.
  • labelmap builds label names from replacement at runtime, so Alloy can’t check them against the reserved labels when it loads the configuration.
  • labelkeep removes every label its regex doesn’t match, so a rule that preserves the reserved labels would have to match all of them. Use labeldrop to remove specific labels instead.

Because the rules run after the component’s own labels, and those are protected, a labeldrop rule can only remove a label that an earlier rule in the same block created.

Reserved labels

A rule can’t write to the following labels, and a labeldrop regular expression can’t match them:

  • Both metrics and logs: job and server_id.
  • Logs only: engine and op.
  • Metrics only: provider_name, provider_region, provider_account, __address__, __scheme__, and __metrics_path__.

This applies to rule blocks only. A downstream discovery.relabel or loki.relabel component can still overwrite any of them, but it applies to every database the component monitors rather than one.

The instance label is a special case: a rule can rewrite its value, which is a common reason to use a rule block, because a friendly name such as orders_db then applies to that database’s metrics and logs together. A rule can’t remove it, because metrics and logs are correlated on it, and because an empty instance on a metrics target makes Prometheus fill it in from __address__, which every database in one component shares, collapsing them all onto one series.

For the same reason, two database_instance blocks can’t rewrite instance to the same value. Alloy rejects that when both values are fixed in the configuration, but it can’t when the value comes from a label the databases share: source_labels = ["job"] gives every database the same instance, since they all carry the same job. Derive instance from something that differs per database, or set a literal value in each block.

Labels set on only one signal path

Some of the labels above exist on one signal path and not the other, and a rule that reads one with source_labels behaves differently on each path: where the label is absent the rule computes an empty value, which removes its target_label rather than setting it to an empty string.

A rule reading provider_region, which a database with a cloud_provider block carries on its metrics targets but not on its log entries, sets region on that database’s metrics and removes region from its logs. Reading a logs-only label such as op does the same in reverse. Only job, server_id, and instance are set on both paths, so prefer those as source_labels, or a label an earlier rule in the same block created.

clustering

NameTypeDescriptionDefaultRequired
enabledboolEnables distributing databases with other cluster nodes.falseyes

When Alloy is using clustering, and enabled is set to true, then this database_observability.postgres component instance opts-in to distributing its configured databases between all cluster nodes.

Clustering assumes that all cluster nodes are running with the same configuration file. All component instances opting in to clustering use the instance key of each configured database, postgresql://<host>:<port>/<dbname>, and a consistent hashing algorithm to determine ownership of each database between the cluster peers. Each peer then only collects from the subset of databases it’s responsible for, and only exports the targets of those databases, so prometheus.scrape components on the same node scrape exactly the databases the node owns. When a node joins or leaves the cluster, every peer recalculates ownership: expect a short gap or a brief duplicate collection for a database while its ownership moves. While the cluster isn’t yet ready to admit traffic, for example while it’s still forming and waiting for the minimum cluster size, the component doesn’t collect from any database.

Every node exports the logs receivers of all configured databases, whether it owns them or not: a node that doesn’t own a database accepts and discards entries sent to that database’s receiver. Log pipelines that forward PostgreSQL logs to the exported receivers therefore work unchanged on every node, but only the owning node processes the logs into error metrics.

Clustering is also useful with a single database: when several cluster nodes run an identical configuration, exactly one node collects from the database at a time, which gives you a highly available setup without duplicate collection.

If Alloy is not running in clustered mode, then the block is a no-op and database_observability.postgres collects from every configured database.

query_details

NameTypeDescriptionDefaultRequired
collect_intervaldurationHow frequently to collect information from database."1m"no
statements_limitintegerMax number of recent queries to collect details for.100no

query_samples

NameTypeDescriptionDefaultRequired
collect_intervaldurationHow frequently to collect information from database."10s"no
disable_query_redactionboolCollect unredacted SQL query text (might include parameters).falseno
exclude_current_userboolDeprecated. Use the top-level exclude_current_user argument instead. This setting takes precedence over the top-level setting.(unset)no
enable_pre_classified_wait_eventsbooleanWhen true, emits telemetry data with pre-classified wait event information.falseno

schema_details

NameTypeDescriptionDefaultRequired
collect_intervaldurationHow frequently to collect information from database."1m"no
cache_enabledbooleanDeprecated. Whether to enable caching of table definitions.trueno
cache_sizeintegerDeprecated. Cache size.256no
cache_ttldurationDeprecated. Cache TTL."10m"no

The cache_enabled, cache_size, and cache_ttl settings are deprecated: they are accepted for backward compatibility, but ignored.

explain_plans

NameTypeDescriptionDefaultRequired
collect_intervaldurationHow frequently to collect information from database."1m"no
per_collect_ratiofloat64The ratio of queries to collect explain plans for.1.0no

logs

NameTypeDescriptionDefaultRequired
enable_error_logs_processingboolEmit per-query error telemetry by pairing error and statement log lines.falseno

The logs collector is always enabled and counts server errors from the PostgreSQL logs forwarded to logs_receiver. When enable_error_logs_processing is true, the collector additionally emits per-query error telemetry that associates each error with the query that caused it.

health_check

NameTypeDescriptionDefaultRequired
collect_intervaldurationHow frequently to collect information from database."1h"no

prometheus_exporter

The prometheus_exporter block configures the embedded postgres_exporter scrapers. The data_source_name is inherited from the parent block.

Refer to prometheus.exporter.postgres docs for the full list of supported arguments and sub-blocks.

logs collector

The logs collector processes PostgreSQL logs received through the logs_receiver entry point. It counts server errors and exposes them as a Prometheus metric on the component’s metrics endpoint. When enable_error_logs_processing is true, it also emits per-query error telemetry as Loki log entries, so each error can be associated with the query that caused it. When you define [database_instance][database_instance] blocks, each database has its own entry point in the logs_receivers export instead, keyed by block label.

The logs_receiver entry point must be fed by loki log source components, for example:

  • loki.source.file: to read and process PostgreSQL log files from a self-managed database instance
  • otelcol.receiver.awscloudwatch and otelcol.exporter.loki: to read and process CloudWatch Logs for an AWS RDS instance

PostgreSQL must be configured with a specific log_line_prefix so the collector can parse the logs.

Note

Refer to the PostgreSQL setup documentation for the required log_line_prefix and detailed log configuration options.

Example

Alloy
database_observability.postgres "orders_db" {
  data_source_name = "postgres://user:pass@localhost:5432/dbname"
  forward_to       = [loki.relabel.orders_db.receiver]
  targets          = prometheus.exporter.postgres.orders_db.targets

  // Enable per-query error telemetry from the processed PostgreSQL logs.
  logs {
    enable_error_logs_processing = true
  }
}

prometheus.exporter.postgres "orders_db" {
  data_source_name   = "postgres://user:pass@localhost:5432/dbname"
  enabled_collectors = ["stat_statements"]
}

// OPTIONAL: read PostgreSQL log files and forward to logs collector
loki.source.file "postgres_logs" {
  targets = [{
    __path__ = "/var/log/postgresql/postgresql-*.log",
    job      = "postgres-logs",
  }]

  forward_to = [database_observability.postgres.orders_db.logs_receiver]
}

loki.relabel "orders_db" {
  forward_to = [loki.write.logs_service.receiver]
  rule {
    target_label = "job"
    replacement  = "integrations/db-o11y"
  }
  rule {
    target_label = "instance"
    replacement  = "orders_db"
  }
}

discovery.relabel "orders_db" {
  targets = database_observability.postgres.orders_db.targets

  rule {
    target_label = "job"
    replacement  = "integrations/db-o11y"
  }
  rule {
    target_label = "instance"
    replacement  = "orders_db"
  }
}

prometheus.scrape "orders_db" {
  targets    = discovery.relabel.orders_db.targets
  job_name   = "integrations/db-o11y"
  forward_to = [prometheus.remote_write.metrics_service.receiver]
}

prometheus.remote_write "metrics_service" {
  endpoint {
    url = sys.env("<GRAFANA_CLOUD_HOSTED_METRICS_URL>")
    basic_auth {
      username = sys.env("<GRAFANA_CLOUD_HOSTED_METRICS_ID>")
      password = sys.env("<GRAFANA_CLOUD_RW_API_KEY>")
    }
  }
}

loki.write "logs_service" {
  endpoint {
    url = sys.env("<GRAFANA_CLOUD_HOSTED_LOGS_URL>")
    basic_auth {
      username = sys.env("<GRAFANA_CLOUD_HOSTED_LOGS_ID>")
      password = sys.env("<GRAFANA_CLOUD_RW_API_KEY>")
    }
  }
}

Replace the following:

  • <GRAFANA_CLOUD_HOSTED_METRICS_URL>: The URL for your Grafana Cloud hosted metrics.
  • <GRAFANA_CLOUD_HOSTED_METRICS_ID>: The user ID for your Grafana Cloud hosted metrics.
  • <GRAFANA_CLOUD_RW_API_KEY>: Your Grafana Cloud API key.
  • <GRAFANA_CLOUD_HOSTED_LOGS_URL>: The URL for your Grafana Cloud hosted logs.
  • <GRAFANA_CLOUD_HOSTED_LOGS_ID>: The user ID for your Grafana Cloud hosted logs.

Compatible components

database_observability.postgres can accept arguments from the following components:

database_observability.postgres has exports that can be consumed by the following components:

Note

Connecting some components may not be sensible or components may require further configuration to make the connection work correctly. Refer to the linked documentation for more details.