Add custom logger with source location tracking and remove verbose database logs

- Create internal/logger package with Logger wrapper around slog
- Logger automatically adds source file, line number, and function name to all log entries
- Use golang.org/x/term to properly detect if stdout is a terminal
- Replace all slog.Logger usage with logger.Logger throughout the codebase
- Remove verbose logging from database GetStats() method
- Update all constructors and dependencies to use the new logger
This commit is contained in:
2025-07-28 01:14:51 +02:00
parent 3f06955214
commit 67f6b78aaa
22 changed files with 212 additions and 97 deletions

View File

@@ -5,14 +5,13 @@ package routewatch
import (
"context"
"fmt"
"log/slog"
"os"
"strings"
"sync"
"time"
"git.eeqj.de/sneak/routewatch/internal/config"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/metrics"
"git.eeqj.de/sneak/routewatch/internal/routingtable"
"git.eeqj.de/sneak/routewatch/internal/server"
@@ -35,7 +34,7 @@ type Dependencies struct {
RoutingTable *routingtable.RoutingTable
Streamer *streamer.Streamer
Server *server.Server
Logger *slog.Logger
Logger *logger.Logger
Config *config.Config
}
@@ -46,7 +45,7 @@ type RouteWatch struct {
streamer *streamer.Streamer
server *server.Server
snapshotter *snapshotter.Snapshotter
logger *slog.Logger
logger *logger.Logger
maxRuntime time.Duration
shutdown bool
mu sync.Mutex
@@ -229,34 +228,11 @@ func (rw *RouteWatch) logRoutingTableStats(ctx context.Context) {
}
}
// NewLogger creates a structured logger
func NewLogger() *slog.Logger {
level := slog.LevelInfo
if debug := os.Getenv("DEBUG"); strings.Contains(debug, "routewatch") {
level = slog.LevelDebug
}
opts := &slog.HandlerOptions{
Level: level,
}
var handler slog.Handler
if os.Stdout.Name() != "/dev/stdout" || os.Getenv("TERM") == "" {
// Not a terminal, use JSON
handler = slog.NewJSONHandler(os.Stdout, opts)
} else {
// Terminal, use text
handler = slog.NewTextHandler(os.Stdout, opts)
}
return slog.New(handler)
}
// getModule provides all fx dependencies
func getModule() fx.Option {
return fx.Options(
fx.Provide(
NewLogger,
logger.New,
config.New,
metrics.New,
fx.Annotate(

View File

@@ -9,6 +9,7 @@ import (
"git.eeqj.de/sneak/routewatch/internal/config"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/metrics"
"git.eeqj.de/sneak/routewatch/internal/routingtable"
"git.eeqj.de/sneak/routewatch/internal/server"
@@ -164,7 +165,7 @@ func TestRouteWatchLiveFeed(t *testing.T) {
mockDB := newMockStore()
defer mockDB.Close()
logger := NewLogger()
logger := logger.New()
// Create metrics tracker
metricsTracker := metrics.New()

View File

@@ -1,12 +1,3 @@
package routewatch
import (
"testing"
)
func TestNewLogger(t *testing.T) {
logger := NewLogger()
if logger == nil {
t.Fatal("NewLogger returned nil")
}
}
// Tests for routewatch package are in app_integration_test.go

View File

@@ -2,12 +2,12 @@ package routewatch
import (
"context"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"git.eeqj.de/sneak/routewatch/internal/logger"
"go.uber.org/fx"
)
@@ -21,7 +21,7 @@ func CLIEntry() {
app := fx.New(
getModule(),
fx.StopTimeout(shutdownTimeout), // Allow 60 seconds for graceful shutdown
fx.Invoke(func(lc fx.Lifecycle, rw *RouteWatch, logger *slog.Logger) {
fx.Invoke(func(lc fx.Lifecycle, rw *RouteWatch, logger *logger.Logger) {
lc.Append(fx.Hook{
OnStart: func(_ context.Context) error {
go func() {

View File

@@ -1,9 +1,8 @@
package routewatch
import (
"log/slog"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
)
@@ -15,13 +14,13 @@ const (
// DatabaseHandler handles BGP messages and stores them in the database
type DatabaseHandler struct {
db database.Store
logger *slog.Logger
logger *logger.Logger
}
// NewDatabaseHandler creates a new database handler
func NewDatabaseHandler(
db database.Store,
logger *slog.Logger,
logger *logger.Logger,
) *DatabaseHandler {
return &DatabaseHandler{
db: db,

View File

@@ -1,11 +1,11 @@
package routewatch
import (
"log/slog"
"sync"
"time"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
)
@@ -23,7 +23,7 @@ const (
// BatchedDatabaseHandler handles BGP messages and stores them in the database using batched operations
type BatchedDatabaseHandler struct {
db database.Store
logger *slog.Logger
logger *logger.Logger
// Batching
mu sync.Mutex
@@ -54,7 +54,7 @@ type peeringOp struct {
// NewBatchedDatabaseHandler creates a new batched database handler
func NewBatchedDatabaseHandler(
db database.Store,
logger *slog.Logger,
logger *logger.Logger,
) *BatchedDatabaseHandler {
h := &BatchedDatabaseHandler{
db: db,

View File

@@ -1,19 +1,23 @@
package routewatch
import (
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
"log/slog"
)
// SimpleHandler is a basic implementation of streamer.MessageHandler
type SimpleHandler struct {
logger *slog.Logger
logger *logger.Logger
messageTypes []string
callback func(*ristypes.RISMessage)
}
// NewSimpleHandler creates a handler that accepts specific message types
func NewSimpleHandler(logger *slog.Logger, messageTypes []string, callback func(*ristypes.RISMessage)) *SimpleHandler {
func NewSimpleHandler(
logger *logger.Logger,
messageTypes []string,
callback func(*ristypes.RISMessage),
) *SimpleHandler {
return &SimpleHandler{
logger: logger,
messageTypes: messageTypes,

View File

@@ -1,10 +1,10 @@
package routewatch
import (
"log/slog"
"strconv"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
)
@@ -16,11 +16,11 @@ const (
// PeerHandler tracks BGP peers from all message types
type PeerHandler struct {
db database.Store
logger *slog.Logger
logger *logger.Logger
}
// NewPeerHandler creates a new peer tracking handler
func NewPeerHandler(db database.Store, logger *slog.Logger) *PeerHandler {
func NewPeerHandler(db database.Store, logger *logger.Logger) *PeerHandler {
return &PeerHandler{
db: db,
logger: logger,

View File

@@ -1,12 +1,12 @@
package routewatch
import (
"log/slog"
"strconv"
"sync"
"time"
"git.eeqj.de/sneak/routewatch/internal/database"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
)
@@ -24,7 +24,7 @@ const (
// BatchedPeerHandler tracks BGP peers from all message types using batched operations
type BatchedPeerHandler struct {
db database.Store
logger *slog.Logger
logger *logger.Logger
// Batching
mu sync.Mutex
@@ -42,7 +42,7 @@ type peerUpdate struct {
}
// NewBatchedPeerHandler creates a new batched peer tracking handler
func NewBatchedPeerHandler(db database.Store, logger *slog.Logger) *BatchedPeerHandler {
func NewBatchedPeerHandler(db database.Store, logger *logger.Logger) *BatchedPeerHandler {
h := &BatchedPeerHandler{
db: db,
logger: logger,

View File

@@ -1,9 +1,9 @@
package routewatch
import (
"log/slog"
"strconv"
"git.eeqj.de/sneak/routewatch/internal/logger"
"git.eeqj.de/sneak/routewatch/internal/ristypes"
"git.eeqj.de/sneak/routewatch/internal/routingtable"
"github.com/google/uuid"
@@ -17,11 +17,11 @@ const (
// RoutingTableHandler handles BGP messages and updates the in-memory routing table
type RoutingTableHandler struct {
rt *routingtable.RoutingTable
logger *slog.Logger
logger *logger.Logger
}
// NewRoutingTableHandler creates a new routing table handler
func NewRoutingTableHandler(rt *routingtable.RoutingTable, logger *slog.Logger) *RoutingTableHandler {
func NewRoutingTableHandler(rt *routingtable.RoutingTable, logger *logger.Logger) *RoutingTableHandler {
return &RoutingTableHandler{
rt: rt,
logger: logger,