A modular Go library providing production-ready building blocks for backend services: structured logging, Prometheus metrics with health probes, PostgreSQL transaction management, and pointer utilities. Built around context.Context propagation and interface-driven design for easy testing.
go get github.com/bdtfs/golib
| Package | Import Path | Description |
|---|---|---|
| clog | github.com/bdtfs/golib/clog |
Context-aware structured JSON logging on top of slog |
| metrics | github.com/bdtfs/golib/metrics |
Dynamic Prometheus counters/histograms, HTTP server, Kubernetes health probes |
| transactions/pgxpool | github.com/bdtfs/golib/transactions/pgxpool |
Transaction manager for jackc/pgx/v5 |
| transactions/pgv10 | github.com/bdtfs/golib/transactions/pgv10 |
Transaction manager for go-pg/pg/v10 |
| pointer | github.com/bdtfs/golib/pointer |
Generic safe-dereference and ref helpers |
Context-aware structured logging built on Go's log/slog. Outputs JSON, routes errors to a separate writer, and propagates fields through context.Context using copy-on-write semantics (no mutexes on the hot path).
Two method families following Go convention (Print / Printf):
- Structured (
InfoCtx,ErrorCtx, ...) -- args are slog-style key-value pairs - Formatted (
InfofCtx,ErrorfCtx, ...) -- args arefmt.Sprintfarguments
logger := clog.NewCustomLogger(os.Stdout, os.Stderr, true, slog.LevelInfo)
// Attach fields to context -- they appear in every subsequent log line
ctx := logger.AddKeysValuesToCtx(ctx, map[string]interface{}{
"user_id": userID,
"request_id": reqID,
})
// Structured: key-value pairs
logger.InfoCtx(ctx, "order placed", "order_id", orderID)
logger.ErrorCtx(ctx, err, "payment failed", "provider", "stripe")
// Formatted: fmt.Sprintf style
logger.InfofCtx(ctx, "order %s placed", orderID)
logger.ErrorfCtx(ctx, err, "payment failed for user %d", userID)Interface: CLog -- swap in clog.NewCLogStub() for silent tests.
Benchmark ns/op B/op allocs/op
clog (structured) 415 48 1
clog (formatted) 419 72 2
clog (disabled level) 3.2 0 0
slog 337 48 1
zap 56 4 0
Dynamic Prometheus metrics collection with an HTTP server that exposes /metrics, /healthz, and /readyz.
Create counters and histograms on the fly. Metrics are registered lazily with the Prometheus registry.
reg := metrics.NewRegistry("api", "myapp")Series ties together a type, sub-type, operation name, and labels to produce consistent metric names.
series := metrics.NewSeries(metrics.SeriesTypeAPIHandler, "v1").
WithLabels(prometheus.Labels{"region": "us-east"})
ctx, series = series.WithOperation(ctx, "getUser")
// Record success count
name, labels := series.Success()
reg.Inc(name, labels)
// Record duration
name, labels, dur := series.Duration(time.Since(start))
reg.RecordDuration(name, labels, dur)Built-in series types: SeriesTypeRPCHandler, SeriesTypeAPIHandler, SeriesTypeUseCase, SeriesTypeClient, SeriesTypeMiddleware, SeriesTypeDB, SeriesTypePostgres, SeriesTypeDatabusConsumer, SeriesTypeServer.
health := metrics.NewHealthChecker(logger)
server := metrics.NewServer(logger, reg, health, ":9090", 5*time.Second)
go server.Start(ctx)
defer server.Stop(ctx)
// Toggle readiness/liveness from your app
health.SetReady(true)
health.SetHealthy(true)Endpoints:
| Path | Purpose |
|---|---|
GET /metrics |
Prometheus scrape target (includes Go runtime memory stats) |
GET /healthz |
Kubernetes liveness probe |
GET /readyz |
Kubernetes readiness probe |
Testing: metrics.NewRegistryStub() -- no-op registry that discards all writes.
Transaction manager for jackc/pgx/v5. Wraps a pgxpool.Pool, propagates the active transaction via context, and handles commit/rollback automatically.
pool, _ := pgxpool.New(ctx, connString)
factory := pgxpool.NewPgTransactionFactory(pool)
manager := pgxpool.NewPgTransactionManager(factory)
err := manager.Do(ctx, func(ctx context.Context) error {
tx := factory.Transaction(ctx) // returns the active pgx.Tx
_, err := tx.Exec(ctx, "INSERT INTO orders (id) VALUES ($1)", orderID)
return err
// commits on nil error, rolls back otherwise
})The Transaction interface exposes Exec, Query, QueryRow, and SendBatch -- matching the common subset of pgxpool.Pool and pgx.Tx, so the same repository code works inside or outside a transaction.
Testing: pgxpool.NewTrmStub() -- executes the callback directly without a real transaction.
Transaction manager for the older go-pg/pg/v10 ORM. Same context-propagation pattern as the pgxpool variant, plus an AlwaysRollback option useful for integration tests.
factory := pgv10.NewPgTransactionFactory(db)
manager := pgv10.NewPgTransactionManager(factory, pgv10.Options{
AlwaysRollback: false, // set true in tests to auto-rollback
})
err := manager.Do(ctx, func(ctx context.Context) error {
tx := factory.Transaction(ctx) // returns orm.DB (the active tx, or the bare db)
_, err := tx.Model(&order).Insert()
return err
})Testing: pgv10.NewTrmStub().
Generic helpers for pointer operations.
// Safe dereference -- returns zero value when nil
var p *int
v := pointer.SafeDeref(p) // 0
// Create a pointer to a value
ptr := pointer.Ref(42) // *int pointing to 42// Logging
logger := clog.NewCustomLogger(os.Stdout, os.Stderr, false, slog.LevelDebug)
// Metrics + health
reg := metrics.NewRegistry("api", "myservice")
health := metrics.NewHealthChecker(logger)
server := metrics.NewServer(logger, reg, health, ":8081", 5*time.Second)
go server.Start(ctx)
// Transactions
pool, _ := pgxpool.New(ctx, connString)
txFactory := pgxpool.NewPgTransactionFactory(pool)
txManager := pgxpool.NewPgTransactionManager(txFactory)
// Request handler
series := metrics.NewSeries(metrics.SeriesTypeAPIHandler, "v1")
ctx, series = series.WithOperation(ctx, "createOrder")
start := time.Now()
err := txManager.Do(ctx, func(ctx context.Context) error {
tx := txFactory.Transaction(ctx)
_, err := tx.Exec(ctx, "INSERT INTO orders (id) VALUES ($1)", orderID)
return err
})
if err != nil {
name, labels := series.Error(err.Error())
reg.Inc(name, labels)
logger.ErrorCtx(ctx, err, "order creation failed", "order_id", orderID)
} else {
name, labels := series.Success()
reg.Inc(name, labels)
}
name, labels, dur := series.Duration(time.Since(start))
reg.RecordDuration(name, labels, dur)
health.SetReady(true)MIT