Skip to content

Commit 2ffb451

Browse files
committed
♻ feat(logging): add log level configuration and improve error
handling
1 parent 4b3ae50 commit 2ffb451

File tree

5 files changed

+101
-64
lines changed

5 files changed

+101
-64
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ FROM gcr.io/distroless/static:nonroot
22

33
COPY semantic-search-api /semantic-search-api
44

5+
ENV LOG_LEVEL=warn
6+
57
ENTRYPOINT [ "/semantic-search-api" ]

cmd/main.go

Lines changed: 97 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,26 @@ import (
2121
)
2222

2323
type config struct {
24-
port int
25-
apiKey string
26-
migrate bool
27-
db struct {
24+
port int
25+
apiKey string
26+
migrate bool
27+
logLevel string
28+
db struct {
2829
dsn string
2930
redis string
3031
}
3132
}
3233

3334
func main() {
3435
cfg := loadConfig()
35-
logger := setupLogger()
36+
logger := setupLogger(cfg.logLevel)
3637

3738
if cfg.migrate {
38-
runMigrations(cfg, logger)
39+
err := runMigrations(cfg, logger)
40+
if err != nil {
41+
logger.Error("Migration failed", "error", err)
42+
os.Exit(1)
43+
}
3944
return
4045
}
4146

@@ -45,11 +50,50 @@ func main() {
4550
}
4651

4752
ctx := context.Background()
48-
dbpool := connectToPostgres(ctx, cfg, logger)
53+
54+
dbpool, err := connectToPostgres(ctx, cfg, logger)
55+
if err != nil {
56+
logger.Error("Database connection failed", "error", err)
57+
os.Exit(1)
58+
}
4959
defer dbpool.Close()
5060

51-
embedder := setupEmbedder(ctx, cfg, logger)
52-
startServer(cfg, logger, dbpool, embedder)
61+
embedder, err := newEmbedder(ctx, cfg, logger)
62+
if err != nil {
63+
logger.Error("Failed to setup embedder", "error", err)
64+
os.Exit(1)
65+
}
66+
67+
repo := repository.New(dbpool)
68+
bookService := &service.BookService{
69+
Embedder: embedder,
70+
Repository: repo,
71+
Logger: logger,
72+
}
73+
bookHandler := &api.BookHandler{
74+
Service: bookService,
75+
}
76+
77+
e := echo.New()
78+
e.HideBanner = true
79+
80+
e.Use(middleware.Logger())
81+
e.Use(middleware.Recover())
82+
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
83+
Timeout: 5 * time.Second,
84+
ErrorMessage: "Request timed out.",
85+
OnTimeoutRouteErrorHandler: func(err error, c echo.Context) {
86+
logger.Warn("Timeout on route", "path", c.Path(), "error", err)
87+
},
88+
}))
89+
90+
e.GET("/ping", func(c echo.Context) error {
91+
return c.String(200, "pong")
92+
})
93+
e.GET("/search", bookHandler.SearchBooks)
94+
e.POST("/books", bookHandler.AddBook)
95+
96+
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.port)))
5397
}
5498

5599
func loadConfig() config {
@@ -76,82 +120,75 @@ Flags:
76120
flag.StringVar(&cfg.db.dsn, "db-dsn", defaultDSN, "PostgreSQL DSN (or set DATABASE_URL env)")
77121
flag.StringVar(&cfg.db.redis, "redis", defaultRedisURL, "Redis URL (optional, or set REDIS_URL env)")
78122
flag.BoolVar(&cfg.migrate, "migrate", false, "Run DB migrations and exit")
123+
flag.StringVar(&cfg.logLevel, "loglevel", "info", "Log level (debug|info|warn|error)")
79124

80125
flag.Parse()
126+
127+
if cfg.db.dsn == "" {
128+
fmt.Fprintln(os.Stderr, "Error: --db-dsn is required")
129+
os.Exit(1)
130+
}
131+
132+
if cfg.apiKey == "" {
133+
fmt.Fprintln(os.Stderr, "Error: --apikey is required")
134+
os.Exit(1)
135+
}
136+
81137
return cfg
82138
}
83139

84-
func setupLogger() *slog.Logger {
85-
return slog.New(tint.NewHandler(os.Stdout, &tint.Options{
86-
Level: slog.LevelInfo,
87-
}))
140+
func setupLogger(levelStr string) *slog.Logger {
141+
level := slog.LevelInfo
142+
143+
switch levelStr {
144+
case "debug":
145+
level = slog.LevelDebug
146+
case "info":
147+
level = slog.LevelInfo
148+
case "warn", "warning":
149+
level = slog.LevelWarn
150+
case "error":
151+
level = slog.LevelError
152+
default:
153+
fmt.Fprintf(os.Stderr, "invalid log level %q, defaulting to info\n", levelStr)
154+
}
155+
156+
handler := tint.NewHandler(os.Stdout, &tint.Options{
157+
Level: level,
158+
})
159+
return slog.New(handler)
88160
}
89161

90-
func runMigrations(cfg config, logger *slog.Logger) {
162+
func runMigrations(cfg config, logger *slog.Logger) error {
91163
if err := db.RunMigrations(cfg.db.dsn, logger); err != nil {
92-
logger.Error("Migration failed", "error", err)
93-
os.Exit(1)
164+
return err
94165
}
95166
logger.Info("Migrations applied successfully")
167+
return nil
96168
}
97169

98-
func connectToPostgres(ctx context.Context, cfg config, logger *slog.Logger) *pgxpool.Pool {
170+
func connectToPostgres(ctx context.Context, cfg config, logger *slog.Logger) (*pgxpool.Pool, error) {
99171
dbpool, err := db.NewPool(ctx, cfg.db.dsn, logger)
100172
if err != nil {
101-
logger.Error("Failed to connect to DB", "error", err)
102-
os.Exit(1)
173+
return nil, err
103174
}
104175
logger.Info("Connected to PostgreSQL")
105-
return dbpool
176+
return dbpool, nil
106177
}
107178

108-
func setupEmbedder(ctx context.Context, cfg config, logger *slog.Logger) embed.Embedder {
109-
baseEmbedder, err := embed.NewGeminiEmbedder(ctx, logger, cfg.apiKey)
179+
func newEmbedder(ctx context.Context, cfg config, logger *slog.Logger) (embed.Embedder, error) {
180+
base, err := embed.NewGeminiEmbedder(ctx, logger, cfg.apiKey)
110181
if err != nil {
111-
logger.Error("Failed to initialize embedder", "error", err)
112-
os.Exit(1)
182+
return nil, err
113183
}
114184

115185
if cfg.db.redis != "" {
116186
redisClient := db.NewRedisClient(cfg.db.redis, logger)
117187
return &embed.CachedEmbedder{
118-
Base: baseEmbedder,
188+
Base: base,
119189
Redis: redisClient,
120190
Logger: logger,
121-
}
122-
}
123-
return baseEmbedder
124-
}
125-
126-
func startServer(cfg config, logger *slog.Logger, dbpool *pgxpool.Pool, embedder embed.Embedder) {
127-
repo := repository.New(dbpool)
128-
bookService := &service.BookService{
129-
Embedder: embedder,
130-
Repository: repo,
131-
Logger: logger,
132-
}
133-
bookHandler := &api.BookHandler{
134-
Service: bookService,
191+
}, nil
135192
}
136-
137-
e := echo.New()
138-
e.HideBanner = true
139-
140-
e.Use(middleware.Logger())
141-
e.Use(middleware.Recover())
142-
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
143-
Timeout: 5 * time.Second,
144-
ErrorMessage: "Request timed out.",
145-
OnTimeoutRouteErrorHandler: func(err error, c echo.Context) {
146-
logger.Warn("Timeout on route", "path", c.Path(), "error", err)
147-
},
148-
}))
149-
150-
e.GET("/ping", func(c echo.Context) error {
151-
return c.String(200, "pong")
152-
})
153-
e.GET("/search", bookHandler.SearchBooks)
154-
e.POST("/books", bookHandler.AddBook)
155-
156-
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.port)))
193+
return base, nil
157194
}

internal/db/db.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ func NewPool(ctx context.Context, dsn string, logger *slog.Logger) (*pgxpool.Poo
3535
return nil, fmt.Errorf("failed to ping database: %w", err)
3636
}
3737

38-
logger.Info("Successfully connected to database")
39-
4038
// Check if 'books' table exists
4139
var exists bool
4240
err = pool.QueryRow(ctx, `

internal/embed/cached_embedder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c *CachedEmbedder) Embed(ctx context.Context, input string) ([]float32, er
2727
if err == nil {
2828
var vec []float32
2929
if err := json.Unmarshal(cached, &vec); err == nil {
30-
c.Logger.Info("Embedding cache hit", "query", input)
30+
c.Logger.Debug("Embedding cache hit", "query", input)
3131
return vec, nil
3232
}
3333
c.Logger.Warn("Failed to unmarshal cached embedding", "error", err)

internal/embed/embed.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ func (g *GeminiEmbedder) Embed(ctx context.Context, input string) ([]float32, er
6262
}
6363

6464
embedding := resp.Embeddings[0].Values
65-
g.logger.Info("Embedding success", "length", len(embedding))
65+
g.logger.Debug("Embedding success", "length", len(embedding))
6666
return embedding, nil
6767
}

0 commit comments

Comments
 (0)