Skip to content

bdtfs/golib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

golib

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

Packages

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

clog

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 are fmt.Sprintf arguments
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.

Benchmarks (Apple M4 Pro)

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

metrics

Dynamic Prometheus metrics collection with an HTTP server that exposes /metrics, /healthz, and /readyz.

Registry

Create counters and histograms on the fly. Metrics are registered lazily with the Prometheus registry.

reg := metrics.NewRegistry("api", "myapp")

Series

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.

Server and Health

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.


transactions/pgxpool

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.


transactions/pgv10

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().


pointer

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

Full Integration Example

// 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)

License

MIT

About

Series of Go libraries for use in my personal projects

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors