This is documentation for the next version of Grafana Alloy Documentation. For the latest stable release, go to the latest version.
database_observability.sql_server
EXPERIMENTAL: This is an experimental component. Experimental components are subject to frequent breaking changes, and may be removed with no equivalent replacement. To enable and use an experimental component, you must set the
stability.levelflag toexperimental.
database_observability.sql_server connects to a Microsoft SQL Server instance and collects observability data across every accessible user database on that instance.
The component forwards this data as log entries to Loki receivers and exports targets for Prometheus scraping.
Usage
database_observability.sql_server "<LABEL>" {
data_source_name = <DATA_SOURCE_NAME>
forward_to = [<LOKI_RECEIVERS>]
}Arguments
You can use the following arguments with database_observability.sql_server:
The query_timeout applies separately to each SQL statement.
The timeout includes waiting for an available connection and reading the statement results.
A collection cycle can run multiple statements and can take longer than query_timeout.
The following collectors are configurable:
Blocks
You can use the following blocks with database_observability.sql_server:
No valid configuration blocks found.
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.
When you don’t configure a cloud_provider block, Alloy attempts to detect AWS RDS and Azure SQL hosts from the data_source_name.
aws
The aws block supplies the ARN identifier for the database being monitored.
azure
The azure block supplies the identifying information for the database being monitored.
gcp
The gcp block supplies the identifying information for the GCP Cloud SQL database being monitored.
database_instance
The database_instance block defines one SQL Server instance to monitor.
Repeat the block to monitor several instances with a single component.
The block label must be unique across database_instance blocks and identifies the instance in the component’s metrics endpoint path.
Each database_instance block must also point to a distinct server: two blocks that resolve to the same host, port or named instance, and database name are rejected.
Each database_instance block can also contain a [cloud_provider][cloud_provider] block that applies to that instance only, and [rule][rule] blocks that relabel the metrics and logs of that instance only.
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, apply to every configured instance.
The metrics for each instance 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.
For example:
database_observability.sql_server "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"
}
}
}In this example, the metrics and logs of the orders instance carry environment="production", and those of the billing instance carry environment="staging".
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 instance 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_labelmust 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.hashmodrequiressource_labels, and can’t targetinstance: hashing the empty value gives every database the same result, and hashing intoinstancebuckets 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
replacementof"$1"computes an empty value when a rule has nosource_labels. Sorule { target_label = "environment" }removesenvironmentrather than setting it, and Alloy rejects it. Always setreplacementalongside atarget_label.Alloy can only reject such a rule where the outcome is certain. A rule that reads
source_labelsdepends on the labels present at runtime, so a missing or misspelled source label still removes the target label, except for the reserved labels andinstance, which keep their previous value. Only removal is prevented: a rule that computes a non-empty value still overwrites those labels, which is whyhashmodcan’t targetinstanceat all.
You can use the hashmod, labeldrop, lowercase, replace, and uppercase actions.
The rest aren’t supported:
drop,dropequal,keep, andkeepequalfilter 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 aloki.relabelcomponent on the receiver named inforward_to, with adroprule that matches that database’sinstanceorserver_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 adiscovery.relabelcomponent on the component’s exported targets.labelmapbuilds label names fromreplacementat runtime, so Alloy can’t check them against the reserved labels when it loads the configuration.labelkeepremoves every label itsregexdoesn’t match, so a rule that preserves the reserved labels would have to match all of them. Uselabeldropto 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:
jobandserver_id. - Logs only:
engineandop. - 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
When Alloy is using clustering, and enabled is set to true, then this database_observability.sql_server component instance opts-in to distributing its configured instances 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 instance, <host>:<port>/<database>, and a consistent hashing algorithm to determine ownership of each instance between the cluster peers.
Each peer then only collects from the subset of instances it’s responsible for, and only exports the targets of those instances, so prometheus.scrape components on the same node scrape exactly the instances 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 an instance 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 instance.
Clustering is also useful with a single instance: when several cluster nodes run an identical configuration, exactly one node collects from the instance 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.sql_server collects from every configured instance.
explain_plans
The explain_plans collector reads the execution plan Query Store already captured for each query tracked by the query_metrics collector.
It doesn’t compile or run a fresh plan.
Only queries that query_metrics is currently tracking are eligible.
When query_metrics is disabled, explain_plans produces no output.
The collector checks for a changed plan every collect_interval.
It forwards a log entry only when the plan’s shape has changed since the last entry.
It also forwards a log entry when 30 minutes have passed since the last entry.
This keeps log volume low and ensures a fresh entry at least every 30 minutes.
health_check
The health_check collector periodically verifies that the login has the required grants (VIEW DEFINITION, plus VIEW DATABASE STATE or its SQL Server 2022+ equivalent VIEW DATABASE PERFORMANCE STATE) and that Query Store is enabled and populated, on every database the login can access on the instance. Each check emits a log entry with op="health_status" and a check field identifying it: AlloyVersion, RequiredGrantsPresent, QueryStoreEnabled, or QueryStoreHasRows.
RequiredGrantsPresent and QueryStoreEnabled pass only when true on every database the login can access; when they fail, the log entry’s value lists the non-compliant databases. QueryStoreHasRows passes as soon as any accessible database has Query Store data, since it’s expected for a low-traffic database to have none.
query_details
The query_details collector reads Query Store query text for the database selected in the data_source_name, not every database on the instance.
query_metrics
The query_metrics collector reads Query Store for the database selected in the data_source_name, not every database on the instance.
Configure the data_source_name to select a user database that has Query Store enabled.
When the connected database is a system database such as master, or Query Store is disabled or read-only, the collector skips collection and remains healthy.
The login requires VIEW DATABASE STATE on the connected database. On SQL Server 2022 and later, VIEW DATABASE PERFORMANCE STATE is also sufficient.
query_samples
The query_samples collector only collects requests whose query hash is tracked by the query_metrics collector.
The collector polls live requests and can miss queries shorter than collect_interval, wait events that start and finish between collections, and a query’s first execution before Query Store admits its hash.
For completed requests, the emitted resource counters contain the values from the final observation and can omit work performed after that observation.
The login requires VIEW SERVER STATE on SQL Server 2019 and earlier. On SQL Server 2022 and later, VIEW SERVER PERFORMANCE STATE is also sufficient. Azure SQL Database can restrict the dynamic management views to the current session. In that case, the collector can’t observe other sessions.
schema_details
The collector scans every database that the login can access on the instance and collects schema details from each. Only databases where the login has CONNECT access to catalog views are collected.
Example
database_observability.sql_server "orders_db" {
data_source_name = "sqlserver://user:pass@server:1433"
forward_to = [loki.write.logs_service.receiver]
}
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_LOGS_URL>: The URL for your Grafana Cloud hosted logs.<GRAFANA_CLOUD_HOSTED_LOGS_ID>: The user ID for your Grafana Cloud hosted logs.<GRAFANA_CLOUD_RW_API_KEY>: Your Grafana Cloud API key.
Compatible components
database_observability.sql_server can accept arguments from the following components:
- Components that export Targets
- Components that export Loki
LogsReceiver
database_observability.sql_server has exports that can be consumed by the following components:
- Components that consume Targets
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.


