From b74852a5353b4bf2cb2cc944f329d651fbf99064 Mon Sep 17 00:00:00 2001
From: Joe Adams <github@joeadams.io>
Date: Wed, 23 Aug 2023 17:33:47 -0400
Subject: [PATCH] Delay database connection until scrape (#882)

This no longer returns an error when creating a collector.instance when the database cannot be reached for the version query. This will resolve the entire postgresCollector not being registered for metrics collection when a database is not available. If the version query fails, the scrape will fail.

Resolves #880

Signed-off-by: Joe Adams <github@joeadams.io>
---
 collector/collector.go |  8 ++++++++
 collector/instance.go  | 30 ++++++++++++++++++++++--------
 collector/probe.go     |  8 ++++++++
 3 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/collector/collector.go b/collector/collector.go
index c1bf2af9..e06ef7d1 100644
--- a/collector/collector.go
+++ b/collector/collector.go
@@ -166,6 +166,14 @@ func (p PostgresCollector) Describe(ch chan<- *prometheus.Desc) {
 // Collect implements the prometheus.Collector interface.
 func (p PostgresCollector) Collect(ch chan<- prometheus.Metric) {
 	ctx := context.TODO()
+
+	// Set up the database connection for the collector.
+	err := p.instance.setup()
+	if err != nil {
+		level.Error(p.logger).Log("msg", "Error opening connection to database", "err", err)
+		return
+	}
+
 	wg := sync.WaitGroup{}
 	wg.Add(len(p.Collectors))
 	for name, c := range p.Collectors {
diff --git a/collector/instance.go b/collector/instance.go
index 9b2bbf47..87eb0591 100644
--- a/collector/instance.go
+++ b/collector/instance.go
@@ -22,29 +22,43 @@ import (
 )
 
 type instance struct {
+	dsn     string
 	db      *sql.DB
 	version semver.Version
 }
 
 func newInstance(dsn string) (*instance, error) {
-	i := &instance{}
+	i := &instance{
+		dsn: dsn,
+	}
+
+	// "Create" a database handle to verify the DSN provided is valid.
+	// Open is not guaranteed to create a connection.
 	db, err := sql.Open("postgres", dsn)
 	if err != nil {
 		return nil, err
 	}
+	db.Close()
+
+	return i, nil
+}
+
+func (i *instance) setup() error {
+	db, err := sql.Open("postgres", i.dsn)
+	if err != nil {
+		return err
+	}
 	db.SetMaxOpenConns(1)
 	db.SetMaxIdleConns(1)
 	i.db = db
 
-	version, err := queryVersion(db)
+	version, err := queryVersion(i.db)
 	if err != nil {
-		db.Close()
-		return nil, err
+		return fmt.Errorf("error querying postgresql version: %w", err)
+	} else {
+		i.version = version
 	}
-
-	i.version = version
-
-	return i, nil
+	return nil
 }
 
 func (i *instance) getDB() *sql.DB {
diff --git a/collector/probe.go b/collector/probe.go
index 834c6517..a7630272 100644
--- a/collector/probe.go
+++ b/collector/probe.go
@@ -18,6 +18,7 @@ import (
 	"sync"
 
 	"github.com/go-kit/log"
+	"github.com/go-kit/log/level"
 	"github.com/prometheus-community/postgres_exporter/config"
 	"github.com/prometheus/client_golang/prometheus"
 )
@@ -74,6 +75,13 @@ func (pc *ProbeCollector) Describe(ch chan<- *prometheus.Desc) {
 }
 
 func (pc *ProbeCollector) Collect(ch chan<- prometheus.Metric) {
+	// Set up the database connection for the collector.
+	err := pc.instance.setup()
+	if err != nil {
+		level.Error(pc.logger).Log("msg", "Error opening connection to database", "err", err)
+		return
+	}
+
 	wg := sync.WaitGroup{}
 	wg.Add(len(pc.collectors))
 	for name, c := range pc.collectors {