diff --git a/cmd/sql_exporter/log.go b/cmd/sql_exporter/log.go new file mode 100644 index 00000000..1b3eec7d --- /dev/null +++ b/cmd/sql_exporter/log.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "log/slog" + "os" + + "github.com/prometheus/common/promslog" +) + +type logConfig struct { + logger *slog.Logger + logFileHandler *os.File +} + +// initLogFile opens the log file for writing if a log file is specified. +func initLogFile(logFile string) (*os.File, error) { + if logFile == "" { + return nil, nil + } + logFileHandler, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("error opening log file: %w", err) + } + return logFileHandler, nil +} + +// initLogConfig configures and initializes the logging system. +func initLogConfig(logLevel, logFormat string, logFile string) (*logConfig, error) { + logFileHandler, err := initLogFile(logFile) + if err != nil { + return nil, err + } + + if logFileHandler == nil { + logFileHandler = os.Stderr + } + + promslogConfig := &promslog.Config{ + Level: &promslog.AllowedLevel{}, + Format: &promslog.AllowedFormat{}, + Style: promslog.SlogStyle, + Writer: logFileHandler, + } + + if err := promslogConfig.Level.Set(logLevel); err != nil { + return nil, err + } + + if err := promslogConfig.Format.Set(logFormat); err != nil { + return nil, err + } + // Initialize logger. + logger := promslog.New(promslogConfig) + + return &logConfig{ + logger: logger, + logFileHandler: logFileHandler, + }, nil +} diff --git a/cmd/sql_exporter/main.go b/cmd/sql_exporter/main.go index a73862f1..a9a5f799 100644 --- a/cmd/sql_exporter/main.go +++ b/cmd/sql_exporter/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "log/slog" "net/http" "os" "os/signal" @@ -12,23 +13,19 @@ import ( "github.com/burningalchemist/sql_exporter" cfg "github.com/burningalchemist/sql_exporter/config" - "github.com/go-kit/log" _ "github.com/kardianos/minwinsvc" "github.com/prometheus/client_golang/prometheus" info "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/model" - "github.com/prometheus/common/promlog" "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" - "k8s.io/klog/v2" ) const ( appName string = "sql_exporter" httpReadHeaderTimeout time.Duration = time.Duration(time.Second * 60) - debugMaxLevel klog.Level = 3 ) var ( @@ -40,6 +37,7 @@ var ( configFile = flag.String("config.file", "sql_exporter.yml", "SQL Exporter configuration file path") logFormat = flag.String("log.format", "logfmt", "Set log output format") logLevel = flag.String("log.level", "info", "Set log level") + logFile = flag.String("log.file", "", "Log file to write to, leave empty to write to stderr") ) func init() { @@ -65,21 +63,30 @@ func main() { } // Setup logging. - logger, err := setupLogging(*logLevel, *logFormat) + logConfig, err := initLogConfig(*logLevel, *logFormat, *logFile) if err != nil { fmt.Printf("Error initializing exporter: %s\n", err) os.Exit(1) } + defer func() { + if logConfig.logFileHandler != nil { + logConfig.logFileHandler.Close() + } + }() + + slog.SetDefault(logConfig.logger) + // Override the config.file default with the SQLEXPORTER_CONFIG environment variable if set. if val, ok := os.LookupEnv(cfg.EnvConfigFile); ok { *configFile = val } - klog.Warningf("Starting SQL exporter %s %s", version.Info(), version.BuildContext()) + slog.Warn("Starting SQL exporter", "versionInfo", version.Info(), "buildContext", version.BuildContext()) exporter, err := sql_exporter.NewExporter(*configFile) if err != nil { - klog.Fatalf("Error creating exporter: %s", err) + slog.Error("Error creating exporter", "error", err) + os.Exit(1) } // Start the scrape_errors_total metric drop ticker if configured. @@ -104,16 +111,18 @@ func main() { if err := web.ListenAndServe(server, &web.FlagConfig{ WebListenAddresses: &([]string{*listenAddress}), WebConfigFile: webConfigFile, WebSystemdSocket: OfBool(false), - }, logger); err != nil { - klog.Fatal(err) + }, logConfig.logger); err != nil { + slog.Error("Error starting web server", "error", err) + os.Exit(1) + } } // reloadHandler returns a handler that reloads collector and target data. -func reloadHandler(e sql_exporter.Exporter, configFile string) func(http.ResponseWriter, *http.Request) { +func reloadHandler(e sql_exporter.Exporter, configFile string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := sql_exporter.Reload(e, &configFile); err != nil { - klog.Error(err) + slog.Error("Error reloading collector and target data", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -128,7 +137,7 @@ func signalHandler(e sql_exporter.Exporter, configFile string) { go func() { for range c { if err := sql_exporter.Reload(e, &configFile); err != nil { - klog.Error(err) + slog.Error("Error reloading collector and target data", "error", err) } } }() @@ -141,7 +150,7 @@ func startScrapeErrorsDropTicker(exporter sql_exporter.Exporter, interval model. } ticker := time.NewTicker(time.Duration(interval)) - klog.Warning("Started scrape_errors_total metrics drop ticker: ", interval) + slog.Warn("Started scrape_errors_total metrics drop ticker", "interval", interval) go func() { defer ticker.Stop() for range ticker.C { @@ -149,25 +158,3 @@ func startScrapeErrorsDropTicker(exporter sql_exporter.Exporter, interval model. } }() } - -// setupLogging configures and initializes the logging system. -func setupLogging(logLevel, logFormat string) (log.Logger, error) { - promlogConfig := &promlog.Config{ - Level: &promlog.AllowedLevel{}, - Format: &promlog.AllowedFormat{}, - } - - if err := promlogConfig.Level.Set(logLevel); err != nil { - return nil, err - } - - if err := promlogConfig.Format.Set(logFormat); err != nil { - return nil, err - } - // Overriding the default klog with our go-kit klog implementation. - logger := promlog.New(promlogConfig) - klog.SetLogger(logger) - klog.ClampLevel(debugMaxLevel) - - return logger, nil -} diff --git a/cmd/sql_exporter/promhttp.go b/cmd/sql_exporter/promhttp.go index 5bb88006..4ffd9936 100644 --- a/cmd/sql_exporter/promhttp.go +++ b/cmd/sql_exporter/promhttp.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "log/slog" "net/http" "strconv" "time" @@ -11,7 +12,6 @@ import ( "github.com/burningalchemist/sql_exporter" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" - "k8s.io/klog/v2" ) const ( @@ -46,16 +46,17 @@ func ExporterHandlerFor(exporter sql_exporter.Exporter) http.Handler { case prometheus.MultiError: for _, err := range t { if errors.Is(err, context.DeadlineExceeded) { - klog.Errorf("%s: timeout collecting metrics", err) + slog.Error("Timeout while collecting metrics", "error", err) + } else { - klog.Errorf("Error gathering metrics: %s", err) + slog.Error("Error gathering metrics", "error", err) } } default: - klog.Errorf("Error gathering metrics: %s", err) + slog.Error("Error gathering metrics", "error", err) } if len(mfs) == 0 { - klog.Errorf("%s: %s", noMetricsGathered, err) + slog.Error("No metrics gathered", "error", err) http.Error(w, noMetricsGathered+", "+err.Error(), http.StatusInternalServerError) return } @@ -70,14 +71,15 @@ func ExporterHandlerFor(exporter sql_exporter.Exporter) http.Handler { for _, mf := range mfs { if err := enc.Encode(mf); err != nil { errs = append(errs, err) - klog.Errorf("Error encoding metric family %q: %s", mf.GetName(), err) + slog.Error("Error encoding metric family", "name", mf.GetName(), "error", err) + } } if closer, ok := writer.(io.Closer); ok { closer.Close() } if errs.MaybeUnwrap() != nil && buf.Len() == 0 { - klog.Errorf("%s: %s", noMetricsEncoded, errs) + slog.Error("No metrics encoded", "error", errs) http.Error(w, noMetricsEncoded+", "+errs.Error(), http.StatusInternalServerError) return } @@ -100,9 +102,9 @@ func contextFor(req *http.Request, exporter sql_exporter.Exporter) (context.Cont if err != nil { switch { case errors.Is(err, strconv.ErrSyntax): - klog.Errorf("%s: unsupported value", prometheusHeaderErr) + slog.Error("Failed to parse timeout from Prometheus header", "error", err) case errors.Is(err, strconv.ErrRange): - klog.Errorf("%s: value out of range", prometheusHeaderErr) + slog.Error(prometheusHeaderErr, "error", err) } } else { timeout = time.Duration(timeoutSeconds * float64(time.Second)) @@ -110,8 +112,7 @@ func contextFor(req *http.Request, exporter sql_exporter.Exporter) (context.Cont // Subtract the timeout offset, unless the result would be negative or zero. timeoutOffset := time.Duration(exporter.Config().Globals.TimeoutOffset) if timeoutOffset > timeout { - klog.Errorf("global.scrape_timeout_offset (`%s`) is greater than Prometheus' scraping timeout (`%s`), ignoring", - timeoutOffset, timeout) + slog.Error("global.scrape_timeout_offset is greater than Prometheus' scraping timeout, ignoring", "timeout", timeout, "timeoutOffset", timeoutOffset) } else { timeout -= timeoutOffset } diff --git a/collector.go b/collector.go index d73ffb5f..d2ad144c 100644 --- a/collector.go +++ b/collector.go @@ -4,13 +4,13 @@ import ( "context" "database/sql" "fmt" + "log/slog" "sync" "time" "github.com/burningalchemist/sql_exporter/config" "github.com/burningalchemist/sql_exporter/errors" dto "github.com/prometheus/client_model/go" - "k8s.io/klog/v2" ) // Collector is a self-contained group of SQL queries and metric families to collect from a specific database. It is @@ -64,7 +64,7 @@ func NewCollector(logContext string, cc *config.CollectorConfig, constLabels []* logContext: logContext, } if c.config.MinInterval > 0 { - klog.V(2).Infof("[%s] Non-zero min_interval (%s), using cached collector.", logContext, c.config.MinInterval) + slog.Warn("Non-zero min_interval, using cached collector.", "logContext", logContext, "min_interval", c.config.MinInterval) return newCachingCollector(&c), nil } return &c, nil @@ -114,15 +114,14 @@ func (cc *cachingCollector) Collect(ctx context.Context, conn *sql.DB, ch chan<- ch <- NewInvalidMetric(errors.Wrap(cc.rawColl.logContext, ctx.Err())) return } - klog.Infof("Cache size: %d", len(cc.cache)) + slog.Debug("Cache size", "length", len(cc.cache)) collTime := time.Now() select { case cacheTime := <-cc.cacheSem: // Have the lock. if age := collTime.Sub(cacheTime); age > cc.minInterval || len(cc.cache) == 0 { // Cache contents are older than minInterval, collect fresh metrics, cache them and pipe them through. - klog.V(2).Infof("[%s] Collecting fresh metrics: min_interval=%.3fs cache_age=%.3fs", - cc.rawColl.logContext, cc.minInterval.Seconds(), age.Seconds()) + slog.Debug("Collecting fresh metrics", "logContext", cc.rawColl.logContext, "min_interval", cc.minInterval.Seconds(), "cache_age", age.Seconds()) cacheChan := make(chan Metric, capMetricChan) cc.cache = make([]Metric, 0, len(cc.cache)) go func() { @@ -132,7 +131,7 @@ func (cc *cachingCollector) Collect(ctx context.Context, conn *sql.DB, ch chan<- for metric := range cacheChan { // catch invalid metrics and return them immediately, don't cache them if ctx.Err() != nil { - klog.V(2).Infof("[%s] Context closed, returning invalid metric", cc.rawColl.logContext) + slog.Debug("Context closed, returning invalid metric", "logContext", cc.rawColl.logContext) ch <- NewInvalidMetric(errors.Wrap(cc.rawColl.logContext, ctx.Err())) continue } @@ -142,8 +141,7 @@ func (cc *cachingCollector) Collect(ctx context.Context, conn *sql.DB, ch chan<- } cacheTime = collTime } else { - klog.V(2).Infof("[%s] Returning cached metrics: min_interval=%.3fs cache_age=%.3fs", - cc.rawColl.logContext, cc.minInterval.Seconds(), age.Seconds()) + slog.Debug("Returning cached metrics", "logContext", cc.rawColl.logContext, "min_interval", cc.minInterval.Seconds(), "cache_age", age.Seconds()) for _, metric := range cc.cache { ch <- metric } diff --git a/config/config.go b/config/config.go index 987ab5f6..31847ffe 100644 --- a/config/config.go +++ b/config/config.go @@ -3,12 +3,12 @@ package config import ( "context" "fmt" + "log/slog" "os" "path/filepath" "github.com/sethvargo/go-envconfig" "gopkg.in/yaml.v3" - "k8s.io/klog/v2" ) // MaxInt32 defines the maximum value of allowed integers @@ -32,7 +32,7 @@ var ( // Load attempts to parse the given config file and return a Config object. func Load(configFile string) (*Config, error) { - klog.Infof("Loading configuration from %s", configFile) + slog.Debug("Loading configuration", "file", configFile) buf, err := os.ReadFile(configFile) if err != nil { return nil, err @@ -185,7 +185,7 @@ func (c *Config) loadCollectorFiles() error { // Resolve the glob to actual filenames. cfs, err := filepath.Glob(cfglob) - klog.Infof("External collector files found: %v", len(cfs)) + slog.Debug("External collector files found", "count", len(cfs), "glob", cfglob) if err != nil { // The only error can be a bad pattern. return fmt.Errorf("error resolving collector files for %s: %w", cfglob, err) @@ -205,7 +205,7 @@ func (c *Config) loadCollectorFiles() error { } c.Collectors = append(c.Collectors, &cc) - klog.Infof("Loaded collector '%s' from %s", cc.Name, cf) + slog.Debug("Loaded collector", "name", cc.Name, "file", cf) } } diff --git a/config/target_config.go b/config/target_config.go index 488105a7..ec25f51c 100644 --- a/config/target_config.go +++ b/config/target_config.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "fmt" + "log/slog" + "os" "github.com/aws/aws-sdk-go-v2/aws" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" - "k8s.io/klog/v2" ) // @@ -64,7 +65,8 @@ type AwsSecret struct { func readDSNFromAwsSecretManager(secretName string) Secret { config, err := awsConfig.LoadDefaultConfig(context.TODO(), awsConfig.WithEC2IMDSRegion()) if err != nil { - klog.Fatal(err) + slog.Error("unable to load AWS config", "error", err) + os.Exit(1) } // Create Secrets Manager client @@ -75,12 +77,13 @@ func readDSNFromAwsSecretManager(secretName string) Secret { VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified } - klog.Infof("reading AWS Secret: %s", secretName) + slog.Debug("reading AWS Secret", "name", secretName) result, err := svc.GetSecretValue(context.TODO(), input) if err != nil { // For a list of exceptions thrown, see // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html - klog.Fatal(err.Error()) + slog.Error("unable to read AWS Secret", "error", err) + os.Exit(1) } // Decrypts secret using the associated KMS key. @@ -90,7 +93,8 @@ func readDSNFromAwsSecretManager(secretName string) Secret { jsonErr := json.Unmarshal([]byte(secretString), &awsSecret) if jsonErr != nil { - klog.Fatal(jsonErr) + slog.Error("unable to unmarshal AWS Secret") + os.Exit(1) } return Secret(awsSecret.DSN) } diff --git a/config/util.go b/config/util.go index 009630c3..56f26e10 100644 --- a/config/util.go +++ b/config/util.go @@ -2,10 +2,9 @@ package config import ( "fmt" + "log/slog" "path/filepath" "strings" - - "k8s.io/klog/v2" ) func checkCollectorRefs(collectorRefs []string, ctx string) error { @@ -45,7 +44,7 @@ func resolveCollectorRefs( return nil, fmt.Errorf("unknown collector %q referenced in %s", cref, ctx) } } - klog.Infof("Resolved collectors for %s: %v", ctx, len(resolved)) + slog.Debug("Resolved collectors", "context", ctx, "count", len(resolved)) return resolved, nil } diff --git a/exporter.go b/exporter.go index 6625b861..a9ab1408 100644 --- a/exporter.go +++ b/exporter.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "sync" @@ -12,7 +13,6 @@ import ( dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" - "k8s.io/klog/v2" ) var ( @@ -191,7 +191,7 @@ func (e *exporter) filterTargets(jf []string) { } } if len(filteredTargets) == 0 { - klog.Errorf("No targets found for job filters. Nothing to scrape.") + slog.Error("No targets found for job filters. Nothing to scrape.") } e.targets = filteredTargets } @@ -215,7 +215,7 @@ func (e *exporter) SetJobFilters(filters []string) { // DropErrorMetrics implements Exporter. func (e *exporter) DropErrorMetrics() { scrapeErrorsMetric.Reset() - klog.Info("Dropped scrape_errors_total metric") + slog.Debug("Dropped scrape_errors_total metric") } // registerScrapeErrorMetric registers the metrics for the exporter itself. diff --git a/go.mod b/go.mod index 1b13b9b5..5b95101b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/config v1.28.0 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.2 - github.com/go-kit/log v0.2.1 github.com/go-sql-driver/mysql v1.8.1 github.com/jackc/pgx/v5 v5.7.1 github.com/kardianos/minwinsvc v1.0.2 @@ -16,14 +15,13 @@ require ( github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.60.1 - github.com/prometheus/exporter-toolkit v0.11.0 + github.com/prometheus/exporter-toolkit v0.13.0 github.com/sethvargo/go-envconfig v1.1.0 github.com/snowflakedb/gosnowflake v1.11.2 github.com/vertica/vertica-sql-go v1.3.3 github.com/xo/dburl v0.23.1 google.golang.org/protobuf v1.35.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/klog/v2 v2.70.1 ) require ( @@ -67,8 +65,6 @@ require ( github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.4.1 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -85,6 +81,8 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect @@ -115,5 +113,3 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect ) - -replace k8s.io/klog/v2 => github.com/simonpasquier/klog-gokit/v3 v3.4.0 diff --git a/go.sum b/go.sum index 4a905696..266dbb96 100644 --- a/go.sum +++ b/go.sum @@ -92,12 +92,6 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -157,6 +151,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -186,8 +184,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= -github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= +github.com/prometheus/exporter-toolkit v0.13.0 h1:lmA0Q+8IaXgmFRKw09RldZmZdnvu9wwcDLIXGmTPw1c= +github.com/prometheus/exporter-toolkit v0.13.0/go.mod h1:2uop99EZl80KdXhv/MxVI2181fMcwlsumFOqBecGkG0= github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= @@ -199,8 +197,6 @@ github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/simonpasquier/klog-gokit/v3 v3.4.0 h1:2eD2INbzUHuGNynPP86BCB8H6Lwfp6wlkOcuyTr3VWM= -github.com/simonpasquier/klog-gokit/v3 v3.4.0/go.mod h1:RREVB5Cc6yYHsweRfhUyM1ZP+Odb8ehxLfY8jaiqvjg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/snowflakedb/gosnowflake v1.11.2 h1:eAMsxrCiC6ij5wX3dHx1TQCBOdDmCK062Ir8rndUkRg= diff --git a/query.go b/query.go index d40616ec..007eede8 100644 --- a/query.go +++ b/query.go @@ -4,10 +4,10 @@ import ( "context" "database/sql" "fmt" + "log/slog" "github.com/burningalchemist/sql_exporter/config" "github.com/burningalchemist/sql_exporter/errors" - "k8s.io/klog/v2" ) // Query wraps a sql.Stmt and all the metrics populated from it. It helps extract keys and values from result rows. @@ -96,7 +96,7 @@ func (q *Query) Collect(ctx context.Context, conn *sql.DB, ch chan<- Metric) { dest, err := q.scanDest(rows) if err != nil { if config.IgnoreMissingVals { - klog.V(3).Info(err) + slog.Warn("Ignoring missing values", "logContext", q.logContext) return } ch <- NewInvalidMetric(err) @@ -147,7 +147,7 @@ func (q *Query) scanDest(rows *sql.Rows) ([]any, errors.WithContext) { if err != nil { return nil, errors.Wrap(q.logContext, err) } - klog.V(3).Infof("[%s] Returned columns: %q", q.logContext, columns) + slog.Debug("Returned columns", "logContext", q.logContext, "columns", columns) // Create the slice to scan the row into, with strings for keys and float64s for values. dest := make([]any, 0, len(columns)) have := make(map[string]bool, len(q.columnTypes)) @@ -164,9 +164,9 @@ func (q *Query) scanDest(rows *sql.Rows) ([]any, errors.WithContext) { have[column] = true default: if column == "" { - klog.Infof("[%s] Unnamed column %d returned by query", q.logContext, i) + slog.Debug("Unnamed column", "logContext", q.logContext, "column", i) } else { - klog.Infof("[%s] Extra column %q returned by query", q.logContext, column) + slog.Debug("Extra column returned by query", "logContext", q.logContext, "column", column) } dest = append(dest, new(any)) } @@ -205,17 +205,17 @@ func (q *Query) scanRow(rows *sql.Rows, dest []any) (map[string]any, errors.With switch q.columnTypes[column] { case columnTypeKey: if !dest[i].(*sql.NullString).Valid { - klog.V(3).Infof("[%s] Key column %q is NULL", q.logContext, column) + slog.Warn("Key column is NULL", "logContext", q.logContext, "column", column) } result[column] = *dest[i].(*sql.NullString) case columnTypeTime: if !dest[i].(*sql.NullTime).Valid { - klog.V(3).Infof("[%s] Time column %q is invalid or NULL", q.logContext, column) + slog.Warn("Time column is NULL", "logContext", q.logContext, "column", column) } result[column] = *dest[i].(*sql.NullTime) case columnTypeValue: if !dest[i].(*sql.NullFloat64).Valid { - klog.V(3).Infof("[%s] Value column %q is NULL", q.logContext, column) + slog.Warn("Value column is NULL", "logContext", q.logContext, "column", column) } result[column] = *dest[i].(*sql.NullFloat64) } diff --git a/reload.go b/reload.go index 48ac420d..837f24bc 100644 --- a/reload.go +++ b/reload.go @@ -2,18 +2,18 @@ package sql_exporter import ( "errors" + "log/slog" cfg "github.com/burningalchemist/sql_exporter/config" - "k8s.io/klog/v2" ) // Reload function is used to reload the exporter configuration without restarting the exporter func Reload(e Exporter, configFile *string) error { - klog.Warning("Reloading collectors has started...") - klog.Warning("Connections will not be changed upon the restart of the exporter") + slog.Warn("Reloading collectors has started...") + slog.Warn("Connections will not be changed upon the restart of the exporter") configNext, err := cfg.Load(*configFile) if err != nil { - klog.Errorf("Error reading config file - %v", err) + slog.Error("Error reading config file", "error", err) return err } @@ -24,8 +24,7 @@ func Reload(e Exporter, configFile *string) error { configCurrent.Collectors = configCurrent.Collectors[:0] } configCurrent.Collectors = configNext.Collectors - klog.Infof("Total collector size change: %v -> %v", len(configCurrent.Collectors), - len(configNext.Collectors)) + slog.Debug("Total collector size change", "from", len(configCurrent.Collectors), "to", len(configNext.Collectors)) // Reload targets switch { @@ -41,13 +40,13 @@ func Reload(e Exporter, configFile *string) error { case len(configCurrent.Jobs) > 0 && configNext.Target != nil: return errors.New("changing scrape mode is not allowed. Please restart the exporter") default: - klog.Warning("No target or jobs have been found - nothing to reload") + slog.Warn("No target or jobs have been found - nothing to reload") } return nil } func reloadTarget(e Exporter, nc, cc *cfg.Config) error { - klog.Warning("Recreating target...") + slog.Warn("Recreating target...") // We want to preserve DSN from the previous config revision to avoid any connection changes nc.Target.DSN = cc.Target.DSN @@ -57,19 +56,18 @@ func reloadTarget(e Exporter, nc, cc *cfg.Config) error { target, err := NewTarget("", cc.Target.Name, "", string(cc.Target.DSN), cc.Target.Collectors(), nil, cc.Globals, cc.Target.EnablePing) if err != nil { - klog.Errorf("Error recreating a target - %v", err) + slog.Error("Error recreating a target", "error", err) return err } // Populate the target list e.UpdateTarget([]Target{target}) - klog.Warning("Collectors have been successfully updated for the target") + slog.Warn("Collectors have been successfully updated for the target") return nil } func reloadJobs(e Exporter, nc, cc *cfg.Config) error { - klog.Warning("Recreating jobs...") - + slog.Warn("Recreating jobs...") // We want to preserve `static_configs`` from the previous config revision to avoid any connection changes for _, currentJob := range cc.Jobs { for _, newJob := range nc.Jobs { @@ -89,15 +87,15 @@ func reloadJobs(e Exporter, nc, cc *cfg.Config) error { break } targets = append(targets, job.Targets()...) - klog.Infof("Recreated Job: %s", jobConfigItem.Name) + slog.Debug("Recreated Job", "name", jobConfigItem.Name) } if updateErr != nil { - klog.Errorf("Error recreating jobs - %v", updateErr) + slog.Error("Error recreating jobs", "error", updateErr) return updateErr } e.UpdateTarget(targets) - klog.Warning("Collectors have been successfully updated for the jobs") + slog.Warn("Collectors have been successfully updated for the jobs") return nil } diff --git a/sql.go b/sql.go index aa0fd96f..8c6c0c1c 100644 --- a/sql.go +++ b/sql.go @@ -5,12 +5,12 @@ import ( "database/sql" "errors" "fmt" + "log/slog" "net/url" "os" "time" "github.com/xo/dburl" - "k8s.io/klog/v2" ) // OpenConnection parses a provided DSN, and opens a DB handle ensuring early termination if the context is closed @@ -52,12 +52,7 @@ func OpenConnection(ctx context.Context, logContext, dsn string, maxConns, maxId conn.SetMaxOpenConns(maxConns) conn.SetConnMaxLifetime(maxConnLifetime) - if klog.V(1).Enabled() { - if len(logContext) > 0 { - logContext = fmt.Sprintf("[%s] ", logContext) - } - klog.Infof("%sDatabase handle successfully opened with '%s' driver", logContext, driver) - } + slog.Debug("Database handle successfully opened", "logContext", logContext, "driver", driver) return conn, nil } @@ -103,7 +98,7 @@ func expandEnv(env string) string { if value, ok := os.LookupEnv(env); ok { return value } - klog.Errorf("Environment variable '$%s' is not found, cannot expand", env) + slog.Error("Environment variable is not found, cannot expand", "env", env) return fmt.Sprintf("$%s", env) } return os.Expand(env, lookupFunc) diff --git a/target.go b/target.go index 4f0c4d4c..37c2eaab 100644 --- a/target.go +++ b/target.go @@ -5,6 +5,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "log/slog" "sort" "sync" "time" @@ -14,7 +15,6 @@ import ( "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" - "k8s.io/klog/v2" ) const ( @@ -67,7 +67,7 @@ func NewTarget( if ep == nil { ep = &config.EnablePing } - klog.Infof("[%s] Target ping enabled: %v", logContext, *ep) + slog.Debug("target ping enabled", "logContext", logContext, "enabled", *ep) // Sort const labels by name to ensure consistent ordering. constLabelPairs := make([]*dto.LabelPair, 0, len(constLabels))