feat(logging): add slog-based structured logging

Introduce slog-based structured logging throughout the booking service
and
server handlers. Add configurable log level via LOG_LEVEL environment
variable. Replace legacy log usage with slog and propagate logger to
booking service for improved observability.
This commit is contained in:
Ruidy 2025-09-12 12:17:20 -04:00
parent 2aadb421ef
commit 40d2338c0f
No known key found for this signature in database
GPG key ID: 705C24D202990805
7 changed files with 60 additions and 7 deletions

10
.golangci.yml Normal file
View file

@ -0,0 +1,10 @@
version: "2"
linters:
default: none
enable:
- sloglint
settings:
sloglint:
# Enforce using attributes only.
# This will raise an error for any key-value pair arguments.
attr-only: true

View file

@ -17,6 +17,8 @@ type Config struct {
DatabaseUrl string `env:"DATABASE_URL, required"` DatabaseUrl string `env:"DATABASE_URL, required"`
// Debug enables debug mode when true // Debug enables debug mode when true
Debug bool `env:"DEBUG, default=false"` Debug bool `env:"DEBUG, default=false"`
// LogLevel is the logging level (e.g., debug, info, warn, error)
LogLevel string `env:"LOG_LEVEL, default=info"`
// Origins is the list of allowed origins // Origins is the list of allowed origins
Origins []string `env:"ORIGINS, required"` Origins []string `env:"ORIGINS, required"`
// Port is the HTTP server port number // Port is the HTTP server port number

View file

@ -29,7 +29,7 @@ func JobMonthlyBookingReport() error {
} }
store := booking.NewPgStore(db) store := booking.NewPgStore(db)
service, err := bookingService.NewService(store, nil, ps) service, err := bookingService.NewService(nil, store, nil, ps)
if err != nil { if err != nil {
return fmt.Errorf("error creating booking service: %w", err) return fmt.Errorf("error creating booking service: %w", err)
} }

View file

@ -0,0 +1,30 @@
package logger
import (
"log/slog"
"os"
"strings"
)
var logLevel slog.LevelVar
func getLevel(levelStr string) slog.Level {
switch strings.ToLower(levelStr) {
case "debug":
return slog.LevelDebug
case "warn":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
func New(levelStr string) *slog.Logger {
logLevel.Set(getLevel(levelStr))
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: &logLevel,
}))
}

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"log/slog"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
@ -31,6 +32,8 @@ func handleBookingListPage(bs *booking.Service, hc *config.Host) echo.HandlerFun
bookings = bs.All() bookings = bs.All()
} }
slog.Info("serving bookings", slog.Int("bookings_length", len(bookings)))
bvm := u.Map(bookings, func(b *booking.Line) *view.ListBookingsViewModel { bvm := u.Map(bookings, func(b *booking.Line) *view.ListBookingsViewModel {
return &view.ListBookingsViewModel{ return &view.ListBookingsViewModel{
Id: b.InvoiceNumber(hc), Id: b.InvoiceNumber(hc),

View file

@ -1,7 +1,7 @@
package booking package booking
import ( import (
"log" "log/slog"
"time" "time"
"github.com/rjNemo/rentease/internal/config" "github.com/rjNemo/rentease/internal/config"
@ -46,10 +46,12 @@ type Service struct {
store Store store Store
parser parserClient parser parserClient
pdf PdfClient pdf PdfClient
logger *slog.Logger
} }
func NewService(store Store, parser parserClient, pdf PdfClient) (*Service, error) { func NewService(logger *slog.Logger, store Store, parser parserClient, pdf PdfClient) (*Service, error) {
return &Service{ return &Service{
logger: logger.With(slog.String("component", "booking_service")),
store: store, store: store,
parser: parser, parser: parser,
pdf: pdf, pdf: pdf,
@ -57,6 +59,7 @@ func NewService(store Store, parser parserClient, pdf PdfClient) (*Service, erro
} }
func (bs Service) All() []*Line { func (bs Service) All() []*Line {
bs.logger.Info("fetching all bookings")
return bs.store.All() return bs.store.All()
} }
@ -71,7 +74,7 @@ func (bs Service) Create(From time.Time, To time.Time, Name, PhoneNumber, Email,
b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalId) b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalId)
err := bs.store.Create(b) err := bs.store.Create(b)
if err != nil { if err != nil {
log.Println(err) bs.logger.Info("failed to create booking", slog.Any("err", err))
} }
return b return b
} }
@ -86,7 +89,7 @@ func (bs Service) Update(id int, From time.Time, To time.Time, Name string, Phon
) *Booking { ) *Booking {
b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalId).WithId(id) b := NewBooking(From, To, Name, PhoneNumber, Email, Platform, CustomerNumber, PlatformFees, externalId).WithId(id)
if err := bs.store.Update(b); err != nil { if err := bs.store.Update(b); err != nil {
log.Println(err) bs.logger.Info("failed to create booking", slog.Any("err", err))
} }
return b return b
} }
@ -94,7 +97,7 @@ func (bs Service) Update(id int, From time.Time, To time.Time, Name string, Phon
func (bs Service) Cancel(id int) { func (bs Service) Cancel(id int) {
err := bs.store.Cancel(id) err := bs.store.Cancel(id)
if err != nil { if err != nil {
log.Println(err) bs.logger.Info("failed to create booking", slog.Any("err", err))
} }
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"os" "os"
"os/signal" "os/signal"
@ -11,6 +12,7 @@ import (
"github.com/rjNemo/rentease/assets" "github.com/rjNemo/rentease/assets"
"github.com/rjNemo/rentease/internal/config" "github.com/rjNemo/rentease/internal/config"
"github.com/rjNemo/rentease/internal/driver/database" "github.com/rjNemo/rentease/internal/driver/database"
"github.com/rjNemo/rentease/internal/driver/logger"
"github.com/rjNemo/rentease/internal/driver/parser" "github.com/rjNemo/rentease/internal/driver/parser"
"github.com/rjNemo/rentease/internal/driver/pdf" "github.com/rjNemo/rentease/internal/driver/pdf"
bookingRepo "github.com/rjNemo/rentease/internal/repository/booking" bookingRepo "github.com/rjNemo/rentease/internal/repository/booking"
@ -37,6 +39,9 @@ func run(c context.Context) error {
return err return err
} }
appLogger := logger.New(appConfig.LogLevel)
slog.SetDefault(appLogger)
// init sentry // init sentry
if err := sentry.Init(sentry.ClientOptions{ if err := sentry.Init(sentry.ClientOptions{
Dsn: appConfig.SentryDsn, Dsn: appConfig.SentryDsn,
@ -66,7 +71,7 @@ func run(c context.Context) error {
parsingClient := parser.NewBookingAgentParser() parsingClient := parser.NewBookingAgentParser()
bookingService, err := booking.NewService(bookingStore, parsingClient, pc) bookingService, err := booking.NewService(appLogger, bookingStore, parsingClient, pc)
if err != nil { if err != nil {
return fmt.Errorf("error creating booking service: %w", err) return fmt.Errorf("error creating booking service: %w", err)
} }