Monitor Snowflake Postgres with Grafana
Overview
This guide walks through setting up Postgres monitoring dashboards in Grafana using the Snowflake data source plugin. Grafana connects directly to Snowflake and queries the event table where Postgres logs are stored, giving you real-time dashboards, log exploration, and alerting without any intermediate pipeline.
What you'll build: A Grafana monitoring setup where Snowflake Postgres logs are queried directly from the event table and visualized in dashboards for log exploration, slow query tracking, error monitoring, and connection activity.
What You'll Learn
- How to configure Postgres logging parameters on a Snowflake Postgres instance
- How Snowflake's event table captures and stores Postgres logs
- How to create a dedicated Snowflake role and service user for Grafana
- How to install and configure the Grafana Snowflake data source plugin
- How to build Grafana dashboards for Postgres monitoring
- How to set up Grafana alerts on Postgres log patterns
Prerequisites
- A Snowflake account with
ACCOUNTADMINaccess (needed for grants) - A Snowflake Postgres instance in READY state (or ability to create one)
- A Grafana Cloud account (any tier) or a Grafana Enterprise instance with an activated license
- OpenSSL installed locally (for RSA key generation)
- psql installed locally (for testing connections)
1. Enable Postgres Logging
Snowflake Postgres supports standard PostgreSQL logging parameters. Before flipping switches, it helps to understand what Postgres can log and why it matters.
A Quick Primer on Postgres Logging
A modern Postgres instance produces comprehensive logs covering nearly every facet of database behavior. While logs are the go-to place for finding critical errors, they're also a key tool for application performance monitoring. Here are the parameters you should know about:
Log severity level (log_min_messages): Controls which server messages are logged based on severity — from DEBUG5 (most verbose) up through INFO, NOTICE, WARNING, ERROR, LOG, FATAL, and PANIC. The default is WARNING, which is a sensible baseline for most environments.
What SQL to log (log_statement):
none— Don't log SQL statements (errors and warnings still appear vialog_min_messages)ddl— Log data definition changes only (table, column, and index changes). A good default for production.mod— Log all DDL plus data modifications (INSERT, UPDATE, DELETE)all— Log every SQL statement. Useful for development and debugging but generates high log volume.
Slow query logging (log_min_duration_statement): Captures queries that exceed a time threshold — a great way to surface inefficient queries without logging everything. A setting of 1s or 500ms is a common starting point.
Lock wait logging (log_lock_waits): Logs any time a query waits on a lock longer than deadlock_timeout. This is safe for production with virtually no overhead and helps identify contention.
Temp file logging (log_temp_files): Logs when Postgres spills operations to disk instead of keeping them in memory. Set this to your work_mem value to catch operations that exceed available memory — a signal you may need to tune memory settings, add indexes, or rewrite queries.
Connection logging (log_connections, log_disconnections): Logs session connect and disconnect events. Helpful for auditing and spotting connection churn.
Production vs. development trade-off:
log_statement=allgenerates a lot of log data and is generally not recommended for production workloads. For production,ddlcombined withlog_min_duration_statementfor slow queries gives you the important details without the volume. For this guide, we useallso every test query is visible in Grafana.
For a deeper dive on logging configuration, log formats, rotation, auto_explain, and using logs for performance tuning, see Postgres Logging for Performance Optimization.
Snowflake Postgres log line prefix
The default log_line_prefix on Snowflake Postgres is:
[%p][%b][%v][%x] %q[user=%u,db=%d,app=%a] [%h]
This is a printf-style format string that gets prepended to every log line. Each escape sequence maps to a piece of metadata:
| Escape | Meaning | Example output |
|---|---|---|
%p | Process ID (pid) | 1592908 |
%b | Backend type | client backend |
%v | Virtual transaction ID | 27/2 |
%x | Transaction ID | 0 |
%q | Non-session stop point (everything after %q only prints for session processes) | (controls conditional output) |
%u | Username | snowflake_admin |
%d | Database name | postgres |
%a | Application name | psql |
%h | Client hostname/IP | 34.214.158.144 |
This can be changed with the log_line_prefix setting as needed.
Configure Logging on Your Snowflake Postgres Instance
By default, Snowflake Postgres does not generate logs. You will need to set log_statement to enable them. If you do not have a production instance, you can set this to all for testing log ingestion. For production, review your necessary configurations.
ALTER SYSTEM SET log_statement = 'all';
2. Confirm Logs in the Snowflake Event Table
Snowflake Postgres logs are routed through Snowflake's account-level event table at SNOWFLAKE.TELEMETRY.EVENTS. This table is the bridge between your Postgres instance and Grafana.
After enabling logging (Step 1), run a few queries against your Postgres instance to generate some log data. If you set log_statement to all, here's a sample to run:
CREATE TABLE cookie_monster ( id SERIAL PRIMARY KEY, cookie_type VARCHAR(100), flavor VARCHAR(50), crunchiness INT CHECK (crunchiness BETWEEN 1 AND 10), nom_rating DECIMAL(3,1), eaten_at TIMESTAMP DEFAULT NOW() ); INSERT INTO cookie_monster (cookie_type, flavor, crunchiness, nom_rating) VALUES ('Chocolate Chip', 'chocolate', 8, 9.5), ('Snickerdoodle', 'cinnamon', 6, 8.7), ('Oatmeal Raisin', 'oat', 7, 7.2), ('Double Chocolate', 'dark chocolate', 9, 9.8), ('Peanut Butter', 'peanut', 5, 8.1); SELECT * FROM cookie_monster WHERE crunchiness > 7; SELECT cookie_type, nom_rating FROM cookie_monster ORDER BY nom_rating DESC; SELECT AVG(crunchiness) AS avg_crunch, MAX(nom_rating) AS best_nom FROM cookie_monster;
Now verify that logs appear in the event table:
-- Replace the instance ID with your Postgres instance's ID -- Find it with: SHOW POSTGRES INSTANCES; (look at the "id" column) SELECT TIMESTAMP, RESOURCE_ATTRIBUTES['instance.id']::STRING AS instance_id, VALUE['MESSAGE']::STRING AS message FROM SNOWFLAKE.TELEMETRY.EVENTS WHERE TIMESTAMP > DATEADD('hour', -1, CURRENT_TIMESTAMP()) AND RECORD_TYPE = 'LOG' AND RESOURCE_ATTRIBUTES['instance.id']::STRING = '<YOUR_INSTANCE_ID>' ORDER BY TIMESTAMP DESC LIMIT 20;
Tip: To find your Postgres instance ID, run
SHOW POSTGRES INSTANCES;and look at theidcolumn. It's a string like4jypgsndvzd5ta6ufaryx6owja.
3. Create the Snowflake Role and Grants
Grafana connects to Snowflake as a dedicated role with specific privileges. This role needs access to the event table and a warehouse to run queries.
USE ROLE ACCOUNTADMIN; -- Create the Grafana role CREATE ROLE IF NOT EXISTS GRAFANA; GRANT ROLE GRAFANA TO ROLE ACCOUNTADMIN; -- Grant IMPORTED PRIVILEGES on the SNOWFLAKE database -- Required for Grafana to read the event table. -- USAGE alone is NOT sufficient — you must use IMPORTED PRIVILEGES. GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE GRAFANA; -- Grant warehouse usage (replace with your warehouse name) GRANT USAGE ON WAREHOUSE MY_WAREHOUSE TO ROLE GRAFANA;
Create a Proxy View for Grafana
The Grafana Snowflake plugin requires a database and schema context for its connection. The SNOWFLAKE.TELEMETRY schema uses IMPORTED PRIVILEGES, which is incompatible with the plugin's USE SCHEMA connection test. To work around this, create a dedicated database with a view that references the event table:
-- Create a database and schema for Grafana CREATE DATABASE IF NOT EXISTS GRAFANA_DB; CREATE SCHEMA IF NOT EXISTS GRAFANA_DB.MONITORING; -- Create a view that filters to Postgres syslog events CREATE OR REPLACE VIEW GRAFANA_DB.MONITORING.POSTGRES_LOGS AS SELECT * FROM SNOWFLAKE.TELEMETRY.EVENTS WHERE RECORD_TYPE = 'LOG' AND RESOURCE_ATTRIBUTES['service.name']::STRING = 'postgres_syslog'; -- Grant access to the Grafana role GRANT USAGE ON DATABASE GRAFANA_DB TO ROLE GRAFANA; GRANT USAGE ON SCHEMA GRAFANA_DB.MONITORING TO ROLE GRAFANA; GRANT SELECT ON VIEW GRAFANA_DB.MONITORING.POSTGRES_LOGS TO ROLE GRAFANA;
This view also simplifies your Grafana queries — you won't need to repeat the RECORD_TYPE and service.name filters in every panel.
Verify the grants:
SHOW GRANTS TO ROLE GRAFANA;
4. Generate RSA Key Pair
The Grafana Snowflake plugin supports RSA key-pair authentication, which is more secure than password auth for service accounts. You'll generate a key pair, give the public key to Snowflake, and give the private key to Grafana.
Generate the Keys
mkdir -p ~/.snowflake/grafana_keys # Generate an unencrypted RSA private key (PKCS#8 format) openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out ~/.snowflake/grafana_keys/grafana_rsa_key.p8 -nocrypt # Extract the public key openssl rsa -in ~/.snowflake/grafana_keys/grafana_rsa_key.p8 -pubout -out ~/.snowflake/grafana_keys/grafana_rsa_key.pub
Get the Public Key Value (for Snowflake)
# Print the public key without the header/footer lines (this is what Snowflake expects) grep -v "BEGIN\|END" ~/.snowflake/grafana_keys/grafana_rsa_key.pub | tr -d '\n'
Copy the output — you'll paste it into the ALTER USER command in the next step.
Get the Private Key (for Grafana)
# Print the full private key including headers (Grafana needs the complete PEM block) cat ~/.snowflake/grafana_keys/grafana_rsa_key.p8
Copy this entire output including the -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- lines — you'll paste it into Grafana.
5. Create the Snowflake Service User
Create a dedicated service user for Grafana. This user authenticates with the RSA key pair (no password).
Key distinction:
GRAFANAis the role.GRAFANA_USERis the user. When configuring the Grafana data source, you'll enterGRAFANA_USERas the username andGRAFANAas the role.
USE ROLE ACCOUNTADMIN; CREATE USER IF NOT EXISTS GRAFANA_USER TYPE = SERVICE DEFAULT_ROLE = GRAFANA DEFAULT_WAREHOUSE = MY_WAREHOUSE DEFAULT_NAMESPACE = GRAFANA_DB.MONITORING; -- Assign the RSA public key (paste the key value from Step 4) ALTER USER GRAFANA_USER SET RSA_PUBLIC_KEY = '<paste_public_key_here>'; -- Grant the GRAFANA role to the user GRANT ROLE GRAFANA TO USER GRAFANA_USER;
6. Install and Configure the Grafana Snowflake Data Source
Unlike other log pipeline services, Grafana queries Snowflake directly — there's no separate ingestion pipeline. The Snowflake data source plugin connects to your account and runs SQL queries on demand when you load a dashboard or explore logs.
Install the Plugin
Grafana Cloud: The Snowflake plugin is available in the plugin catalog. Go to Connections > Add new connection, search for "Snowflake", and click Install.
Self-managed Grafana Enterprise: Install via the CLI:
grafana-cli plugins install grafana-snowflake-datasource
Then restart Grafana.
Configure the Data Source
- In Grafana, go to Connections > Data sources
- Click Add new data source
- Search for and select Snowflake
- Fill in the fields:
| Field | Value |
|---|---|
| Account | Your Snowflake account identifier (e.g., myorg-myaccount) |
| Username | GRAFANA_USER |
| Authentication Type | Key Pair |
| Private Key | Paste the full PEM private key from Step 4 |
| Role | GRAFANA |
| Warehouse | MY_WAREHOUSE (your warehouse name) |
| Database | GRAFANA_DB |
| Schema | MONITORING |
Important: Use the
GRAFANA_DBdatabase andMONITORINGschema (the proxy view from Step 3) — notSNOWFLAKE/TELEMETRY. The Grafana plugin's connection test runsUSE SCHEMA, which fails against schemas accessed viaIMPORTED PRIVILEGES. The proxy view avoids this issue entirely.
- Click Save & test
You should see "Data source is working."
7. Explore Postgres Logs in Grafana
With the data source connected, you can start exploring Postgres logs immediately using Grafana's Explore view.
Basic Log Exploration
- Go to Explore in Grafana
- Select your Snowflake data source
- Set the visualization to Table (log queries return text, not numeric data for graphs)
- Enter the following query to see recent Postgres logs:
SELECT TIMESTAMP AS time, VALUE['SEVERITY_TEXT']::STRING AS level, VALUE['MESSAGE']::STRING AS message, RESOURCE_ATTRIBUTES['instance.id']::STRING AS instance_id FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) ORDER BY TIMESTAMP DESC
Grafana macros:
$__timeFilter(TIMESTAMP)is a Grafana macro that expands to a time range filter on the specified column, based on the time picker in the dashboard UI. This replaces the need to manually writeTIMESTAMP > $__timeFrom AND TIMESTAMP < $__timeTo.
Filter by Instance
If you have multiple Postgres instances, add an instance filter:
SELECT TIMESTAMP AS time, VALUE['SEVERITY_TEXT']::STRING AS level, VALUE['MESSAGE']::STRING AS message FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) AND RESOURCE_ATTRIBUTES['instance.id']::STRING = '<YOUR_INSTANCE_ID>' ORDER BY TIMESTAMP DESC
Errors and Warnings Only
SELECT TIMESTAMP AS time, VALUE['SEVERITY_TEXT']::STRING AS level, VALUE['MESSAGE']::STRING AS message FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) AND VALUE['SEVERITY_TEXT']::STRING IN ('ERROR', 'FATAL', 'PANIC', 'WARNING') ORDER BY TIMESTAMP DESC
8. Build a Postgres Monitoring Dashboard
Create a dedicated dashboard to monitor your Postgres instances. Go to Dashboards > New dashboard and add panels using the queries below.
Panel: Log Volume Over Time
Visualization type: Time series
SELECT TIME_SLICE(TIMESTAMP, 5, 'MINUTE') AS time, COUNT(*) AS log_count FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) GROUP BY time ORDER BY time;
Panel: Log Volume by Severity
Visualization type: Time series (stacked)
SELECT TIME_SLICE(TIMESTAMP, 5, 'MINUTE') AS time, VALUE['SEVERITY_TEXT']::STRING AS level, COUNT(*) AS log_count FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) GROUP BY time, level ORDER BY time;
Panel: Error Log Feed
Visualization type: Table
SELECT TIMESTAMP AS time, VALUE['SEVERITY_TEXT']::STRING AS level, VALUE['MESSAGE']::STRING AS message, RESOURCE_ATTRIBUTES['instance.id']::STRING AS instance_id FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) AND VALUE['SEVERITY_TEXT']::STRING IN ('ERROR', 'FATAL', 'PANIC') ORDER BY TIMESTAMP DESC LIMIT 100;
Panel: Connections Over Time
If you have log_connections enabled, you can track connection activity:
SELECT TIME_SLICE(TIMESTAMP, 5, 'MINUTE') AS time, COUNT(*) AS connections FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) AND VALUE['MESSAGE']::STRING ILIKE '%connection authorized%' GROUP BY time ORDER BY time;
Panel: Slow Queries
If you have log_min_duration_statement set, duration-logged queries appear in the logs:
SELECT TIMESTAMP AS time, VALUE['MESSAGE']::STRING AS message, RESOURCE_ATTRIBUTES['instance.id']::STRING AS instance_id FROM POSTGRES_LOGS WHERE $__timeFilter(TIMESTAMP) AND VALUE['MESSAGE']::STRING ILIKE '%duration:%' ORDER BY TIMESTAMP DESC LIMIT 50;
Dashboard Variable: Instance Picker
To make the dashboard work across multiple Postgres instances, add a template variable:
- Go to Dashboard settings > Variables > New variable
- Set:
- Name:
instance_id - Type: Query
- Data source: Your Snowflake data source
- Query:
- Name:
SELECT DISTINCT RESOURCE_ATTRIBUTES['instance.id']::STRING AS instance_id FROM POSTGRES_LOGS WHERE TIMESTAMP > DATEADD('day', -1, CURRENT_TIMESTAMP())
Then replace '<YOUR_INSTANCE_ID>' in your panel queries with '${instance_id}' to make them dynamic.
9. Set Up Alerts
Grafana alerting lets you get notified when something goes wrong in your Postgres instances.
Alert: Error Spike
- Go to Alerting > Alert rules > New alert rule
- Select your Snowflake data source
- Use this query:
SELECT COUNT(*) AS error_count FROM POSTGRES_LOGS WHERE TIMESTAMP > DATEADD('minute', -5, CURRENT_TIMESTAMP()) AND VALUE['SEVERITY_TEXT']::STRING IN ('ERROR', 'FATAL', 'PANIC')
- Set the condition:
error_count > 10(adjust the threshold to your environment) - Configure a notification channel (Slack, email, PagerDuty, etc.)
Alert: FATAL or PANIC Events
For critical events, you may want immediate notification with a lower threshold:
SELECT COUNT(*) AS critical_count FROM POSTGRES_LOGS WHERE TIMESTAMP > DATEADD('minute', -5, CURRENT_TIMESTAMP()) AND VALUE['SEVERITY_TEXT']::STRING IN ('FATAL', 'PANIC')
Set the condition to critical_count > 0 so any FATAL or PANIC event triggers a notification.
Evaluation interval: Grafana evaluates alert rules on a schedule. Set the evaluation interval to match how frequently you want checks to run (e.g., every 1 or 5 minutes). Keep in mind that each evaluation runs a query against your Snowflake warehouse.
Conclusion and Resources
You now have a Grafana monitoring setup that queries Postgres logs directly from the Snowflake event table. Because Grafana pulls data on demand rather than requiring a separate ingestion pipeline, you can start exploring logs immediately and iterate on dashboards without any additional infrastructure.
What You Learned
- How to configure Postgres logging parameters on a Snowflake Postgres instance
- How the Snowflake event table captures and stores Postgres logs
- How to create a dedicated Snowflake role and service user for Grafana
- How to install and configure the Grafana Snowflake data source plugin with key-pair auth
- How to build monitoring dashboards with log volume, error feeds, and slow query panels
- How to set up Grafana alerts on Postgres log patterns
Resources
This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances