Summit 26 from June 1-4 in San Francisco

Lead your organization in the era of agents and enterprise intelligence.

Snowflake for DevelopersGuidesMonitor Snowflake Postgres with Grafana
Quickstart

Monitor Snowflake Postgres with Grafana

Elizabeth Christensen

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 ACCOUNTADMIN access (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 via log_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=all generates a lot of log data and is generally not recommended for production workloads. For production, ddl combined with log_min_duration_statement for slow queries gives you the important details without the volume. For this guide, we use all so 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:

EscapeMeaningExample output
%pProcess ID (pid)1592908
%bBackend typeclient backend
%vVirtual transaction ID27/2
%xTransaction ID0
%qNon-session stop point (everything after %q only prints for session processes)(controls conditional output)
%uUsernamesnowflake_admin
%dDatabase namepostgres
%aApplication namepsql
%hClient hostname/IP34.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 the id column. It's a string like 4jypgsndvzd5ta6ufaryx6owja.

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: GRAFANA is the role. GRAFANA_USER is the user. When configuring the Grafana data source, you'll enter GRAFANA_USER as the username and GRAFANA as 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

  1. In Grafana, go to Connections > Data sources
  2. Click Add new data source
  3. Search for and select Snowflake
  4. Fill in the fields:
FieldValue
AccountYour Snowflake account identifier (e.g., myorg-myaccount)
UsernameGRAFANA_USER
Authentication TypeKey Pair
Private KeyPaste the full PEM private key from Step 4
RoleGRAFANA
WarehouseMY_WAREHOUSE (your warehouse name)
DatabaseGRAFANA_DB
SchemaMONITORING

Important: Use the GRAFANA_DB database and MONITORING schema (the proxy view from Step 3) — not SNOWFLAKE / TELEMETRY. The Grafana plugin's connection test runs USE SCHEMA, which fails against schemas accessed via IMPORTED PRIVILEGES. The proxy view avoids this issue entirely.

  1. 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

  1. Go to Explore in Grafana
  2. Select your Snowflake data source
  3. Set the visualization to Table (log queries return text, not numeric data for graphs)
  4. 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 write TIMESTAMP > $__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:

  1. Go to Dashboard settings > Variables > New variable
  2. Set:
    • Name: instance_id
    • Type: Query
    • Data source: Your Snowflake data source
    • Query:
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

  1. Go to Alerting > Alert rules > New alert rule
  2. Select your Snowflake data source
  3. 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')
  1. Set the condition: error_count > 10 (adjust the threshold to your environment)
  2. 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

Updated 2026-04-07

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances