database: create shared database package with configurable patterns
Extract ~200 lines of duplicate database connection code from api/ntpdb/ and monitor/ntpdb/ into common/database/ package. Creates foundation for database consolidation while maintaining zero breaking changes. Files added: - config.go: Unified configuration with package-specific defaults - connector.go: Dynamic connector pattern from Boostport - pool.go: Configurable connection pool management - metrics.go: Optional Prometheus metrics integration - interfaces.go: Shared database interfaces for consistent patterns Key features: - Configuration-driven approach (API: 25/10 connections + metrics, Monitor: 10/5 connections, no metrics) - Optional Prometheus metrics when registerer provided - Backward compatibility via convenience functions - Flexible config file loading (explicit paths + search-based) Dependencies: Added mysql driver and yaml parsing for database configuration.
This commit is contained in:
parent
c372d79d1d
commit
96afb77844
61
database/config.go
Normal file
61
database/config.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents the database configuration structure
|
||||||
|
type Config struct {
|
||||||
|
MySQL DBConfig `yaml:"mysql"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBConfig represents the MySQL database configuration
|
||||||
|
type DBConfig struct {
|
||||||
|
DSN string `default:"" flag:"dsn" usage:"Database DSN"`
|
||||||
|
User string `default:"" flag:"user"`
|
||||||
|
Pass string `default:"" flag:"pass"`
|
||||||
|
DBName string // Optional database name override
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigOptions allows customization of database opening behavior
|
||||||
|
type ConfigOptions struct {
|
||||||
|
// ConfigFiles is a list of config file paths to search for database configuration
|
||||||
|
ConfigFiles []string
|
||||||
|
|
||||||
|
// EnablePoolMonitoring enables connection pool metrics collection
|
||||||
|
EnablePoolMonitoring bool
|
||||||
|
|
||||||
|
// PrometheusRegisterer for metrics collection. If nil, no metrics are collected.
|
||||||
|
PrometheusRegisterer prometheus.Registerer
|
||||||
|
|
||||||
|
// Connection pool settings
|
||||||
|
MaxOpenConns int
|
||||||
|
MaxIdleConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfigOptions returns the standard configuration options used by API package
|
||||||
|
func DefaultConfigOptions() ConfigOptions {
|
||||||
|
return ConfigOptions{
|
||||||
|
ConfigFiles: []string{"database.yaml", "/vault/secrets/database.yaml"},
|
||||||
|
EnablePoolMonitoring: true,
|
||||||
|
PrometheusRegisterer: prometheus.DefaultRegisterer,
|
||||||
|
MaxOpenConns: 25,
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
ConnMaxLifetime: 3 * time.Minute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitorConfigOptions returns configuration options optimized for Monitor package
|
||||||
|
func MonitorConfigOptions() ConfigOptions {
|
||||||
|
return ConfigOptions{
|
||||||
|
ConfigFiles: []string{"database.yaml", "/vault/secrets/database.yaml"},
|
||||||
|
EnablePoolMonitoring: false, // Monitor doesn't need metrics
|
||||||
|
PrometheusRegisterer: nil, // No Prometheus dependency
|
||||||
|
MaxOpenConns: 10,
|
||||||
|
MaxIdleConns: 5,
|
||||||
|
ConnMaxLifetime: 3 * time.Minute,
|
||||||
|
}
|
||||||
|
}
|
88
database/connector.go
Normal file
88
database/connector.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// from https://github.com/Boostport/dynamic-database-config
|
||||||
|
|
||||||
|
// CreateConnectorFunc is a function that creates a database connector
|
||||||
|
type CreateConnectorFunc func() (driver.Connector, error)
|
||||||
|
|
||||||
|
// Driver implements the sql/driver interface with dynamic configuration
|
||||||
|
type Driver struct {
|
||||||
|
CreateConnectorFunc CreateConnectorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver returns the driver instance
|
||||||
|
func (d Driver) Driver() driver.Driver {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect creates a new database connection using the dynamic connector
|
||||||
|
func (d Driver) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
connector, err := d.CreateConnectorFunc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating connector from function: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return connector.Connect(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open is not supported for dynamic configuration
|
||||||
|
func (d Driver) Open(name string) (driver.Conn, error) {
|
||||||
|
return nil, errors.New("open is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// createConnector creates a connector function that reads configuration from a file
|
||||||
|
func createConnector(configFile string) CreateConnectorFunc {
|
||||||
|
return func() (driver.Connector, error) {
|
||||||
|
dbFile, err := os.Open(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dbFile.Close()
|
||||||
|
|
||||||
|
dec := yaml.NewDecoder(dbFile)
|
||||||
|
cfg := Config{}
|
||||||
|
|
||||||
|
err = dec.Decode(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := cfg.MySQL.DSN
|
||||||
|
if len(dsn) == 0 {
|
||||||
|
dsn = os.Getenv("DATABASE_DSN")
|
||||||
|
if len(dsn) == 0 {
|
||||||
|
return nil, fmt.Errorf("dsn config in database.yaml or DATABASE_DSN environment variable required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbcfg, err := mysql.ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user := cfg.MySQL.User; len(user) > 0 {
|
||||||
|
dbcfg.User = user
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass := cfg.MySQL.Pass; len(pass) > 0 {
|
||||||
|
dbcfg.Passwd = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
if name := cfg.MySQL.DBName; len(name) > 0 {
|
||||||
|
dbcfg.DBName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysql.NewConnector(dbcfg)
|
||||||
|
}
|
||||||
|
}
|
34
database/interfaces.go
Normal file
34
database/interfaces.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DBTX matches the interface expected by SQLC-generated code
|
||||||
|
// This interface is implemented by both *sql.DB and *sql.Tx
|
||||||
|
type DBTX interface {
|
||||||
|
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||||
|
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||||
|
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseQuerier provides basic query functionality
|
||||||
|
// This interface should be implemented by package-specific Queries types
|
||||||
|
type BaseQuerier interface {
|
||||||
|
WithTx(tx *sql.Tx) BaseQuerier
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseQuerierTx provides transaction functionality
|
||||||
|
// This interface should be implemented by package-specific Queries types
|
||||||
|
type BaseQuerierTx interface {
|
||||||
|
BaseQuerier
|
||||||
|
Begin(ctx context.Context) (BaseQuerierTx, error)
|
||||||
|
Commit(ctx context.Context) error
|
||||||
|
Rollback(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionFunc represents a function that operates within a database transaction
|
||||||
|
// This is used by the shared transaction helpers in transaction.go
|
||||||
|
type TransactionFunc[Q any] func(ctx context.Context, q Q) error
|
93
database/metrics.go
Normal file
93
database/metrics.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DatabaseMetrics holds the Prometheus metrics for database connection pool monitoring
|
||||||
|
type DatabaseMetrics struct {
|
||||||
|
ConnectionsOpen prometheus.Gauge
|
||||||
|
ConnectionsIdle prometheus.Gauge
|
||||||
|
ConnectionsInUse prometheus.Gauge
|
||||||
|
ConnectionsWaitCount prometheus.Counter
|
||||||
|
ConnectionsWaitDuration prometheus.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabaseMetrics creates a new set of database metrics and registers them
|
||||||
|
func NewDatabaseMetrics(registerer prometheus.Registerer) *DatabaseMetrics {
|
||||||
|
metrics := &DatabaseMetrics{
|
||||||
|
ConnectionsOpen: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "database_connections_open",
|
||||||
|
Help: "Number of open database connections",
|
||||||
|
}),
|
||||||
|
ConnectionsIdle: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "database_connections_idle",
|
||||||
|
Help: "Number of idle database connections",
|
||||||
|
}),
|
||||||
|
ConnectionsInUse: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "database_connections_in_use",
|
||||||
|
Help: "Number of database connections in use",
|
||||||
|
}),
|
||||||
|
ConnectionsWaitCount: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "database_connections_wait_count_total",
|
||||||
|
Help: "Total number of times a connection had to wait",
|
||||||
|
}),
|
||||||
|
ConnectionsWaitDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "database_connections_wait_duration_seconds",
|
||||||
|
Help: "Time spent waiting for a database connection",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if registerer != nil {
|
||||||
|
registerer.MustRegister(
|
||||||
|
metrics.ConnectionsOpen,
|
||||||
|
metrics.ConnectionsIdle,
|
||||||
|
metrics.ConnectionsInUse,
|
||||||
|
metrics.ConnectionsWaitCount,
|
||||||
|
metrics.ConnectionsWaitDuration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitorConnectionPool runs a background goroutine to collect connection pool metrics
|
||||||
|
func monitorConnectionPool(ctx context.Context, db *sql.DB, registerer prometheus.Registerer) {
|
||||||
|
if registerer == nil {
|
||||||
|
return // No metrics collection if no registerer provided
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics := NewDatabaseMetrics(registerer)
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
stats := db.Stats()
|
||||||
|
|
||||||
|
metrics.ConnectionsOpen.Set(float64(stats.OpenConnections))
|
||||||
|
metrics.ConnectionsIdle.Set(float64(stats.Idle))
|
||||||
|
metrics.ConnectionsInUse.Set(float64(stats.InUse))
|
||||||
|
metrics.ConnectionsWaitCount.Add(float64(stats.WaitCount))
|
||||||
|
|
||||||
|
if stats.WaitDuration > 0 {
|
||||||
|
metrics.ConnectionsWaitDuration.Observe(stats.WaitDuration.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log connection pool stats for high usage or waiting
|
||||||
|
if stats.OpenConnections > 20 || stats.WaitCount > 0 {
|
||||||
|
fmt.Printf("Connection pool stats: open=%d idle=%d in_use=%d wait_count=%d wait_duration=%s\n",
|
||||||
|
stats.OpenConnections, stats.Idle, stats.InUse, stats.WaitCount, stats.WaitDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
database/pool.go
Normal file
78
database/pool.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.ntppool.org/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenDB opens a database connection with the specified configuration options
|
||||||
|
func OpenDB(ctx context.Context, options ConfigOptions) (*sql.DB, error) {
|
||||||
|
log := logger.Setup()
|
||||||
|
|
||||||
|
configFile, err := findConfigFile(options.ConfigFiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbconn := sql.OpenDB(Driver{
|
||||||
|
CreateConnectorFunc: createConnector(configFile),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set connection pool parameters
|
||||||
|
dbconn.SetConnMaxLifetime(options.ConnMaxLifetime)
|
||||||
|
dbconn.SetMaxOpenConns(options.MaxOpenConns)
|
||||||
|
dbconn.SetMaxIdleConns(options.MaxIdleConns)
|
||||||
|
|
||||||
|
err = dbconn.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("could not connect to database", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start optional connection pool monitoring
|
||||||
|
if options.EnablePoolMonitoring && options.PrometheusRegisterer != nil {
|
||||||
|
go monitorConnectionPool(ctx, dbconn, options.PrometheusRegisterer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbconn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDBWithConfigFile opens a database connection using an explicit config file path
|
||||||
|
// This is a convenience function for API package compatibility
|
||||||
|
func OpenDBWithConfigFile(ctx context.Context, configFile string) (*sql.DB, error) {
|
||||||
|
options := DefaultConfigOptions()
|
||||||
|
options.ConfigFiles = []string{configFile}
|
||||||
|
return OpenDB(ctx, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDBMonitor opens a database connection with monitor-specific defaults
|
||||||
|
// This is a convenience function for Monitor package compatibility
|
||||||
|
func OpenDBMonitor() (*sql.DB, error) {
|
||||||
|
options := MonitorConfigOptions()
|
||||||
|
return OpenDB(context.Background(), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findConfigFile searches for the first existing config file from the list
|
||||||
|
func findConfigFile(configFiles []string) (string, error) {
|
||||||
|
var firstErr error
|
||||||
|
|
||||||
|
for _, configFile := range configFiles {
|
||||||
|
if configFile == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(configFile); err == nil {
|
||||||
|
return configFile, nil
|
||||||
|
} else if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstErr != nil {
|
||||||
|
return "", fmt.Errorf("no config file found: %w", firstErr)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no valid config files provided")
|
||||||
|
}
|
5
go.mod
5
go.mod
@ -4,10 +4,12 @@ go 1.23.5
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/abh/certman v0.4.0
|
github.com/abh/certman v0.4.0
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
github.com/labstack/echo-contrib v0.17.2
|
github.com/labstack/echo-contrib v0.17.2
|
||||||
github.com/labstack/echo/v4 v4.13.3
|
github.com/labstack/echo/v4 v4.13.3
|
||||||
github.com/oklog/ulid/v2 v2.1.0
|
github.com/oklog/ulid/v2 v2.1.0
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
|
github.com/prometheus/client_model v0.6.1
|
||||||
github.com/remychantenay/slog-otel v1.3.2
|
github.com/remychantenay/slog-otel v1.3.2
|
||||||
github.com/samber/slog-echo v1.14.8
|
github.com/samber/slog-echo v1.14.8
|
||||||
github.com/samber/slog-multi v1.2.4
|
github.com/samber/slog-multi v1.2.4
|
||||||
@ -28,9 +30,11 @@ require (
|
|||||||
golang.org/x/net v0.33.0
|
golang.org/x/net v0.33.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.10.0
|
||||||
google.golang.org/grpc v1.69.2
|
google.golang.org/grpc v1.69.2
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@ -47,7 +51,6 @@ require (
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
|
||||||
github.com/prometheus/common v0.61.0 // indirect
|
github.com/prometheus/common v0.61.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/samber/lo v1.47.0 // indirect
|
github.com/samber/lo v1.47.0 // indirect
|
||||||
|
12
go.sum
12
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/abh/certman v0.4.0 h1:XHoDtb0YyRQPclaHMrBDlKTVZpNjTK6vhB0S3Bd/Sbs=
|
github.com/abh/certman v0.4.0 h1:XHoDtb0YyRQPclaHMrBDlKTVZpNjTK6vhB0S3Bd/Sbs=
|
||||||
github.com/abh/certman v0.4.0/go.mod h1:x8QhpKVZifmV1Hdiwdg9gLo2GMPAxezz1s3zrVnPs+I=
|
github.com/abh/certman v0.4.0/go.mod h1:x8QhpKVZifmV1Hdiwdg9gLo2GMPAxezz1s3zrVnPs+I=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@ -17,6 +19,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
@ -30,6 +34,10 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
|||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w=
|
github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w=
|
||||||
@ -65,6 +73,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
|||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/remychantenay/slog-otel v1.3.2 h1:ZBx8qnwfLJ6e18Vba4e9Xp9B7khTmpIwFsU1sAmActw=
|
github.com/remychantenay/slog-otel v1.3.2 h1:ZBx8qnwfLJ6e18Vba4e9Xp9B7khTmpIwFsU1sAmActw=
|
||||||
github.com/remychantenay/slog-otel v1.3.2/go.mod h1:gKW4tQ8cGOKoA+bi7wtYba/tcJ6Tc9XyQ/EW8gHA/2E=
|
github.com/remychantenay/slog-otel v1.3.2/go.mod h1:gKW4tQ8cGOKoA+bi7wtYba/tcJ6Tc9XyQ/EW8gHA/2E=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
@ -211,6 +221,8 @@ google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7
|
|||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user