logging
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m23s

This commit is contained in:
Dmitri 2026-04-19 19:09:13 +02:00
parent 36cbda5e7a
commit f00226a0d6
Signed by: kanopo
GPG Key ID: 759ADD40E3132AC7
8 changed files with 82 additions and 7 deletions

View File

@ -52,6 +52,8 @@
### Logging ### Logging
- Zap JSON logs - Zap JSON logs
- Uber Zap logger will be initialized in `cmd/api/main.go` and injected into services/middleware (no global loggers).
- Logs will be written directly to a file from the application, with log rotation implemented via `lumberjack`.
- Correlation/request ID in middleware - Correlation/request ID in middleware
- Structured error logging from middleware and service boundaries - Structured error logging from middleware and service boundaries
@ -64,6 +66,7 @@
- SQL-only migrations - SQL-only migrations
- Keep up/down migration scripts - Keep up/down migration scripts
- Run on startup in non-prod optional, required in CI/CD/deploy step - Run on startup in non-prod optional, required in CI/CD/deploy step
- Migrations are securely bundled into the binary using Go's `embed.FS` from a dedicated `migrations` package to isolate them from `internal` db logic.
### SQLC ### SQLC
@ -82,7 +85,10 @@
- `emit_interface: true` (generated `Querier` interface) - `emit_interface: true` (generated `Querier` interface)
- `emit_json_tags: false` (can be revisited if API structs are returned directly) - `emit_json_tags: false` (can be revisited if API structs are returned directly)
- Initial queries implemented for users: `GetUser`, `CreateUser`, `DeleteUser` - Initial queries implemented for users: `GetUser`, `CreateUser`, `DeleteUser`
- DB pool wiring into `cmd/api` and service construction is planned next - **Goose startup migrations** have been wired into `cmd/api/main.go`, utilizing the `embed.FS` strategy and logging via Zap adapter.
- DB pool is successfully wired in `cmd/api`.
- Environment-aware Zap logger is configured (development vs production).
- **Next Planned:** Implement application-level file logging with rotation (using `lumberjack`), and build initial Gin API routes for health checks.
--- ---

BIN
api Executable file

Binary file not shown.

View File

@ -2,16 +2,21 @@ package main
import ( import (
"context" "context"
"log"
"time" "time"
"git.kanopo.dev/rhythm/rhythm-backend/internal/config" "git.kanopo.dev/rhythm/rhythm-backend/internal/config"
"git.kanopo.dev/rhythm/rhythm-backend/internal/db" "git.kanopo.dev/rhythm/rhythm-backend/internal/db"
"git.kanopo.dev/rhythm/rhythm-backend/internal/logger"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
func main() { func main() {
cfg := config.Load() cfg := config.Load()
log := logger.New(cfg.AppEnv)
defer log.Sync()
log.Info("Starting rhythm")
ctx := context.Background() ctx := context.Background()
pool, err := pgxpool.New(ctx, cfg.DbUrl) pool, err := pgxpool.New(ctx, cfg.DbUrl)
if err != nil { if err != nil {
@ -25,9 +30,9 @@ func main() {
if err := pool.Ping(ctx); err != nil { if err := pool.Ping(ctx); err != nil {
log.Fatalf("ping to db failed %v", err.Error()) log.Fatalf("ping to db failed %v", err.Error())
} }
log.Printf("successfully connected to database") log.Info("successfully connected to database")
db.RunMigrations(cfg.DbUrl) db.RunMigrations(cfg.DbUrl, log)
} }
} }

3
go.mod
View File

@ -5,6 +5,7 @@ go 1.26.2
require ( require (
github.com/jackc/pgx/v5 v5.9.2 github.com/jackc/pgx/v5 v5.9.2
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/pressly/goose v2.7.0+incompatible
github.com/pressly/goose/v3 v3.27.0 github.com/pressly/goose/v3 v3.27.0
) )
@ -13,8 +14,10 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect github.com/mfridman/interpolate v0.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
) )

6
go.sum
View File

@ -21,8 +21,12 @@ github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6B
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose v2.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ=
github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@ -36,6 +40,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=

View File

@ -8,6 +8,7 @@ import (
) )
type Config struct { type Config struct {
AppEnv string
DbUrl string DbUrl string
} }
@ -24,7 +25,13 @@ func Load() Config {
dbUrl = fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=disable", username, password, host, port, name) dbUrl = fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=disable", username, password, host, port, name)
} }
appEnv := os.Getenv("APP_ENV")
if appEnv == "" {
appEnv = "development"
}
cfg := Config{ cfg := Config{
AppEnv: appEnv,
DbUrl: dbUrl, DbUrl: dbUrl,
} }

View File

@ -4,17 +4,22 @@ import (
"database/sql" "database/sql"
"log" "log"
"git.kanopo.dev/rhythm/rhythm-backend/internal/logger"
"git.kanopo.dev/rhythm/rhythm-backend/migrations" "git.kanopo.dev/rhythm/rhythm-backend/migrations"
_ "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
"github.com/pressly/goose/v3" "github.com/pressly/goose/v3"
"go.uber.org/zap"
) )
func RunMigrations(dbURL string) { func RunMigrations(dbURL string, zLog *zap.SugaredLogger) {
db, err := sql.Open("pgx", dbURL) db, err := sql.Open("pgx", dbURL)
if err != nil { if err != nil {
log.Fatalf("open db for migrations: %v", err.Error()) log.Fatalf("open db for migrations: %v", err.Error())
} }
defer db.Close() defer db.Close()
goose.SetLogger(&logger.GooseLogger{SugaredLogger: zLog})
// 2. Pass the exported FS to Goose! // 2. Pass the exported FS to Goose!
goose.SetBaseFS(migrations.FS) goose.SetBaseFS(migrations.FS)
if err := goose.SetDialect("postgres"); err != nil { if err := goose.SetDialect("postgres"); err != nil {

43
internal/logger/logger.go Normal file
View File

@ -0,0 +1,43 @@
package logger
import (
"go.uber.org/zap"
"log"
)
func New(env string) *zap.SugaredLogger {
var zapLogger *zap.Logger
var err error
if env == "production" {
zapLogger, err = zap.NewProduction()
} else {
zapLogger, err = zap.NewDevelopment()
}
if err != nil {
log.Fatalf("failed to initialize zap logger: %v", err)
}
return zapLogger.Sugar()
}
type GooseLogger struct {
*zap.SugaredLogger
}
func (l *GooseLogger) Fatal(v ...interface{}) {
l.SugaredLogger.Fatal(v...)
}
func (l *GooseLogger) Fatalf(format string, v ...interface{}) {
l.SugaredLogger.Fatalf(format, v...)
}
func (l *GooseLogger) Print(v ...interface{}) {
l.SugaredLogger.Info(v...)
}
func (l *GooseLogger) Printf(format string, v ...interface{}) {
l.SugaredLogger.Infof(format, v...)
}