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:
parent
3f06955214
commit
67f6b78aaa
@ -9,16 +9,14 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
||||||
"log/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set up logger to only show errors
|
// Set up logger
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
logger := logger.New()
|
||||||
Level: slog.LevelError,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Create metrics tracker
|
// Create metrics tracker
|
||||||
metricsTracker := metrics.New()
|
metricsTracker := metrics.New()
|
||||||
|
1
go.mod
1
go.mod
@ -15,4 +15,5 @@ require (
|
|||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/term v0.33.0 // indirect
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -24,5 +24,7 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
|||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||||
|
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -5,12 +5,12 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/pkg/asinfo"
|
"git.eeqj.de/sneak/routewatch/pkg/asinfo"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
_ "github.com/mattn/go-sqlite3" // CGO SQLite driver
|
_ "github.com/mattn/go-sqlite3" // CGO SQLite driver
|
||||||
@ -24,12 +24,12 @@ const dirPermissions = 0750 // rwxr-x---
|
|||||||
// Database manages the SQLite database connection and operations.
|
// Database manages the SQLite database connection and operations.
|
||||||
type Database struct {
|
type Database struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new database connection and initializes the schema.
|
// New creates a new database connection and initializes the schema.
|
||||||
func New(cfg *config.Config, logger *slog.Logger) (*Database, error) {
|
func New(cfg *config.Config, logger *logger.Logger) (*Database, error) {
|
||||||
dbPath := filepath.Join(cfg.GetStateDir(), "db.sqlite")
|
dbPath := filepath.Join(cfg.GetStateDir(), "db.sqlite")
|
||||||
|
|
||||||
// Log database path
|
// Log database path
|
||||||
@ -348,28 +348,24 @@ func (d *Database) GetStats() (Stats, error) {
|
|||||||
var stats Stats
|
var stats Stats
|
||||||
|
|
||||||
// Count ASNs
|
// Count ASNs
|
||||||
d.logger.Info("Counting ASNs")
|
|
||||||
err := d.queryRow("SELECT COUNT(*) FROM asns").Scan(&stats.ASNs)
|
err := d.queryRow("SELECT COUNT(*) FROM asns").Scan(&stats.ASNs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stats, err
|
return stats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count prefixes
|
// Count prefixes
|
||||||
d.logger.Info("Counting prefixes")
|
|
||||||
err = d.queryRow("SELECT COUNT(*) FROM prefixes").Scan(&stats.Prefixes)
|
err = d.queryRow("SELECT COUNT(*) FROM prefixes").Scan(&stats.Prefixes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stats, err
|
return stats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count IPv4 and IPv6 prefixes
|
// Count IPv4 and IPv6 prefixes
|
||||||
d.logger.Info("Counting IPv4 prefixes")
|
|
||||||
const ipVersionV4 = 4
|
const ipVersionV4 = 4
|
||||||
err = d.queryRow("SELECT COUNT(*) FROM prefixes WHERE ip_version = ?", ipVersionV4).Scan(&stats.IPv4Prefixes)
|
err = d.queryRow("SELECT COUNT(*) FROM prefixes WHERE ip_version = ?", ipVersionV4).Scan(&stats.IPv4Prefixes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stats, err
|
return stats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.logger.Info("Counting IPv6 prefixes")
|
|
||||||
const ipVersionV6 = 6
|
const ipVersionV6 = 6
|
||||||
err = d.queryRow("SELECT COUNT(*) FROM prefixes WHERE ip_version = ?", ipVersionV6).Scan(&stats.IPv6Prefixes)
|
err = d.queryRow("SELECT COUNT(*) FROM prefixes WHERE ip_version = ?", ipVersionV6).Scan(&stats.IPv6Prefixes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -377,14 +373,12 @@ func (d *Database) GetStats() (Stats, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Count peerings
|
// Count peerings
|
||||||
d.logger.Info("Counting peerings")
|
|
||||||
err = d.queryRow("SELECT COUNT(*) FROM asn_peerings").Scan(&stats.Peerings)
|
err = d.queryRow("SELECT COUNT(*) FROM asn_peerings").Scan(&stats.Peerings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stats, err
|
return stats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get database file size
|
// Get database file size
|
||||||
d.logger.Info("Getting database file size")
|
|
||||||
fileInfo, err := os.Stat(d.path)
|
fileInfo, err := os.Stat(d.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Warn("Failed to get database file size", "error", err)
|
d.logger.Warn("Failed to get database file size", "error", err)
|
||||||
@ -393,7 +387,5 @@ func (d *Database) GetStats() (Stats, error) {
|
|||||||
stats.FileSizeBytes = fileInfo.Size()
|
stats.FileSizeBytes = fileInfo.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
d.logger.Info("Stats collection complete")
|
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,15 @@ package database
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log/slog"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const slowQueryThreshold = 50 * time.Millisecond
|
const slowQueryThreshold = 50 * time.Millisecond
|
||||||
|
|
||||||
// logSlowQuery logs queries that take longer than slowQueryThreshold
|
// logSlowQuery logs queries that take longer than slowQueryThreshold
|
||||||
func logSlowQuery(logger *slog.Logger, query string, start time.Time) {
|
func logSlowQuery(logger *logger.Logger, query string, start time.Time) {
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if elapsed > slowQueryThreshold {
|
if elapsed > slowQueryThreshold {
|
||||||
logger.Debug("Slow query", "query", query, "duration", elapsed)
|
logger.Debug("Slow query", "query", query, "duration", elapsed)
|
||||||
@ -47,7 +48,7 @@ func (d *Database) exec(query string, args ...interface{}) error {
|
|||||||
// loggingTx wraps sql.Tx to log slow queries
|
// loggingTx wraps sql.Tx to log slow queries
|
||||||
type loggingTx struct {
|
type loggingTx struct {
|
||||||
*sql.Tx
|
*sql.Tx
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRow wraps sql.Tx.QueryRow to log slow queries
|
// QueryRow wraps sql.Tx.QueryRow to log slow queries
|
||||||
|
150
internal/logger/logger.go
Normal file
150
internal/logger/logger.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Package logger provides a structured logger with source location tracking
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger wraps slog.Logger to add source location information
|
||||||
|
type Logger struct {
|
||||||
|
*slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsSlog returns the underlying slog.Logger
|
||||||
|
func (l *Logger) AsSlog() *slog.Logger {
|
||||||
|
return l.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new logger with appropriate handler based on environment
|
||||||
|
func New() *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 term.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
|
// Terminal, use text
|
||||||
|
handler = slog.NewTextHandler(os.Stdout, opts)
|
||||||
|
} else {
|
||||||
|
// Not a terminal, use JSON
|
||||||
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Logger{Logger: slog.New(handler)}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceSkipLevel = 2 // Skip levels for source location tracking
|
||||||
|
|
||||||
|
// getSourceAttrs returns attributes for the calling source location
|
||||||
|
func getSourceAttrs() []slog.Attr {
|
||||||
|
pc, file, line, ok := runtime.Caller(sourceSkipLevel)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get just the filename without the full path
|
||||||
|
file = filepath.Base(file)
|
||||||
|
|
||||||
|
// Get the function name
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
var funcName string
|
||||||
|
if fn != nil {
|
||||||
|
funcName = filepath.Base(fn.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := []slog.Attr{
|
||||||
|
slog.String("source", file),
|
||||||
|
slog.Int("line", line),
|
||||||
|
}
|
||||||
|
|
||||||
|
if funcName != "" {
|
||||||
|
attrs = append(attrs, slog.String("func", funcName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs at debug level with source location
|
||||||
|
func (l *Logger) Debug(msg string, args ...any) {
|
||||||
|
sourceAttrs := getSourceAttrs()
|
||||||
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
||||||
|
|
||||||
|
// Add source attributes first
|
||||||
|
for _, attr := range sourceAttrs {
|
||||||
|
allArgs = append(allArgs, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user args
|
||||||
|
allArgs = append(allArgs, args...)
|
||||||
|
|
||||||
|
l.Logger.Debug(msg, allArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs at info level with source location
|
||||||
|
func (l *Logger) Info(msg string, args ...any) {
|
||||||
|
sourceAttrs := getSourceAttrs()
|
||||||
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
||||||
|
|
||||||
|
// Add source attributes first
|
||||||
|
for _, attr := range sourceAttrs {
|
||||||
|
allArgs = append(allArgs, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user args
|
||||||
|
allArgs = append(allArgs, args...)
|
||||||
|
|
||||||
|
l.Logger.Info(msg, allArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs at warn level with source location
|
||||||
|
func (l *Logger) Warn(msg string, args ...any) {
|
||||||
|
sourceAttrs := getSourceAttrs()
|
||||||
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
||||||
|
|
||||||
|
// Add source attributes first
|
||||||
|
for _, attr := range sourceAttrs {
|
||||||
|
allArgs = append(allArgs, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user args
|
||||||
|
allArgs = append(allArgs, args...)
|
||||||
|
|
||||||
|
l.Logger.Warn(msg, allArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs at error level with source location
|
||||||
|
func (l *Logger) Error(msg string, args ...any) {
|
||||||
|
sourceAttrs := getSourceAttrs()
|
||||||
|
allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)
|
||||||
|
|
||||||
|
// Add source attributes first
|
||||||
|
for _, attr := range sourceAttrs {
|
||||||
|
allArgs = append(allArgs, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user args
|
||||||
|
allArgs = append(allArgs, args...)
|
||||||
|
|
||||||
|
l.Logger.Error(msg, allArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With returns a new logger with additional attributes
|
||||||
|
func (l *Logger) With(args ...any) *Logger {
|
||||||
|
return &Logger{Logger: l.Logger.With(args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGroup returns a new logger with a group prefix
|
||||||
|
func (l *Logger) WithGroup(name string) *Logger {
|
||||||
|
return &Logger{Logger: l.Logger.WithGroup(name)}
|
||||||
|
}
|
@ -5,14 +5,13 @@ package routewatch
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"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/metrics"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/server"
|
"git.eeqj.de/sneak/routewatch/internal/server"
|
||||||
@ -35,7 +34,7 @@ type Dependencies struct {
|
|||||||
RoutingTable *routingtable.RoutingTable
|
RoutingTable *routingtable.RoutingTable
|
||||||
Streamer *streamer.Streamer
|
Streamer *streamer.Streamer
|
||||||
Server *server.Server
|
Server *server.Server
|
||||||
Logger *slog.Logger
|
Logger *logger.Logger
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ type RouteWatch struct {
|
|||||||
streamer *streamer.Streamer
|
streamer *streamer.Streamer
|
||||||
server *server.Server
|
server *server.Server
|
||||||
snapshotter *snapshotter.Snapshotter
|
snapshotter *snapshotter.Snapshotter
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
maxRuntime time.Duration
|
maxRuntime time.Duration
|
||||||
shutdown bool
|
shutdown bool
|
||||||
mu sync.Mutex
|
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
|
// getModule provides all fx dependencies
|
||||||
func getModule() fx.Option {
|
func getModule() fx.Option {
|
||||||
return fx.Options(
|
return fx.Options(
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
NewLogger,
|
logger.New,
|
||||||
config.New,
|
config.New,
|
||||||
metrics.New,
|
metrics.New,
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"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/metrics"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/server"
|
"git.eeqj.de/sneak/routewatch/internal/server"
|
||||||
@ -164,7 +165,7 @@ func TestRouteWatchLiveFeed(t *testing.T) {
|
|||||||
mockDB := newMockStore()
|
mockDB := newMockStore()
|
||||||
defer mockDB.Close()
|
defer mockDB.Close()
|
||||||
|
|
||||||
logger := NewLogger()
|
logger := logger.New()
|
||||||
|
|
||||||
// Create metrics tracker
|
// Create metrics tracker
|
||||||
metricsTracker := metrics.New()
|
metricsTracker := metrics.New()
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
// Tests for routewatch package are in app_integration_test.go
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewLogger(t *testing.T) {
|
|
||||||
logger := NewLogger()
|
|
||||||
if logger == nil {
|
|
||||||
t.Fatal("NewLogger returned nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,12 +2,12 @@ package routewatch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ func CLIEntry() {
|
|||||||
app := fx.New(
|
app := fx.New(
|
||||||
getModule(),
|
getModule(),
|
||||||
fx.StopTimeout(shutdownTimeout), // Allow 60 seconds for graceful shutdown
|
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{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(_ context.Context) error {
|
OnStart: func(_ context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,13 +14,13 @@ const (
|
|||||||
// DatabaseHandler handles BGP messages and stores them in the database
|
// DatabaseHandler handles BGP messages and stores them in the database
|
||||||
type DatabaseHandler struct {
|
type DatabaseHandler struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabaseHandler creates a new database handler
|
// NewDatabaseHandler creates a new database handler
|
||||||
func NewDatabaseHandler(
|
func NewDatabaseHandler(
|
||||||
db database.Store,
|
db database.Store,
|
||||||
logger *slog.Logger,
|
logger *logger.Logger,
|
||||||
) *DatabaseHandler {
|
) *DatabaseHandler {
|
||||||
return &DatabaseHandler{
|
return &DatabaseHandler{
|
||||||
db: db,
|
db: db,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"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
|
// BatchedDatabaseHandler handles BGP messages and stores them in the database using batched operations
|
||||||
type BatchedDatabaseHandler struct {
|
type BatchedDatabaseHandler struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
|
|
||||||
// Batching
|
// Batching
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@ -54,7 +54,7 @@ type peeringOp struct {
|
|||||||
// NewBatchedDatabaseHandler creates a new batched database handler
|
// NewBatchedDatabaseHandler creates a new batched database handler
|
||||||
func NewBatchedDatabaseHandler(
|
func NewBatchedDatabaseHandler(
|
||||||
db database.Store,
|
db database.Store,
|
||||||
logger *slog.Logger,
|
logger *logger.Logger,
|
||||||
) *BatchedDatabaseHandler {
|
) *BatchedDatabaseHandler {
|
||||||
h := &BatchedDatabaseHandler{
|
h := &BatchedDatabaseHandler{
|
||||||
db: db,
|
db: db,
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
"log/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SimpleHandler is a basic implementation of streamer.MessageHandler
|
// SimpleHandler is a basic implementation of streamer.MessageHandler
|
||||||
type SimpleHandler struct {
|
type SimpleHandler struct {
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
messageTypes []string
|
messageTypes []string
|
||||||
callback func(*ristypes.RISMessage)
|
callback func(*ristypes.RISMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimpleHandler creates a handler that accepts specific message types
|
// 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{
|
return &SimpleHandler{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
messageTypes: messageTypes,
|
messageTypes: messageTypes,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,11 +16,11 @@ const (
|
|||||||
// PeerHandler tracks BGP peers from all message types
|
// PeerHandler tracks BGP peers from all message types
|
||||||
type PeerHandler struct {
|
type PeerHandler struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPeerHandler creates a new peer tracking handler
|
// 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{
|
return &PeerHandler{
|
||||||
db: db,
|
db: db,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ const (
|
|||||||
// BatchedPeerHandler tracks BGP peers from all message types using batched operations
|
// BatchedPeerHandler tracks BGP peers from all message types using batched operations
|
||||||
type BatchedPeerHandler struct {
|
type BatchedPeerHandler struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
|
|
||||||
// Batching
|
// Batching
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@ -42,7 +42,7 @@ type peerUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewBatchedPeerHandler creates a new batched peer tracking handler
|
// 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{
|
h := &BatchedPeerHandler{
|
||||||
db: db,
|
db: db,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -17,11 +17,11 @@ const (
|
|||||||
// RoutingTableHandler handles BGP messages and updates the in-memory routing table
|
// RoutingTableHandler handles BGP messages and updates the in-memory routing table
|
||||||
type RoutingTableHandler struct {
|
type RoutingTableHandler struct {
|
||||||
rt *routingtable.RoutingTable
|
rt *routingtable.RoutingTable
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRoutingTableHandler creates a new routing table handler
|
// 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{
|
return &RoutingTableHandler{
|
||||||
rt: rt,
|
rt: rt,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,6 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ type RoutingTable struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new routing table, loading from snapshot if available
|
// New creates a new routing table, loading from snapshot if available
|
||||||
func New(cfg *config.Config, logger *slog.Logger) *RoutingTable {
|
func New(cfg *config.Config, logger *logger.Logger) *RoutingTable {
|
||||||
rt := &RoutingTable{
|
rt := &RoutingTable{
|
||||||
routes: make(map[RouteKey]*Route),
|
routes: make(map[RouteKey]*Route),
|
||||||
byPrefix: make(map[uuid.UUID]map[RouteKey]*Route),
|
byPrefix: make(map[uuid.UUID]map[RouteKey]*Route),
|
||||||
@ -452,7 +452,7 @@ func isIPv6(prefix string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadFromSnapshot attempts to load the routing table from a snapshot file
|
// loadFromSnapshot attempts to load the routing table from a snapshot file
|
||||||
func (rt *RoutingTable) loadFromSnapshot(logger *slog.Logger) error {
|
func (rt *RoutingTable) loadFromSnapshot(logger *logger.Logger) error {
|
||||||
// If no snapshot directory specified, nothing to load
|
// If no snapshot directory specified, nothing to load
|
||||||
if rt.snapshotDir == "" {
|
if rt.snapshotDir == "" {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package routingtable
|
package routingtable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRoutingTable(t *testing.T) {
|
func TestRoutingTable(t *testing.T) {
|
||||||
// Create a test logger
|
// Create a test logger
|
||||||
logger := slog.Default()
|
logger := logger.New()
|
||||||
|
|
||||||
// Create test config with empty state dir (no snapshot loading)
|
// Create test config with empty state dir (no snapshot loading)
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
@ -129,7 +129,7 @@ func TestRoutingTable(t *testing.T) {
|
|||||||
|
|
||||||
func TestRoutingTableConcurrency(t *testing.T) {
|
func TestRoutingTableConcurrency(t *testing.T) {
|
||||||
// Create a test logger
|
// Create a test logger
|
||||||
logger := slog.Default()
|
logger := logger.New()
|
||||||
|
|
||||||
// Create test config with empty state dir (no snapshot loading)
|
// Create test config with empty state dir (no snapshot loading)
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
@ -189,7 +189,7 @@ func TestRoutingTableConcurrency(t *testing.T) {
|
|||||||
|
|
||||||
func TestRouteUpdate(t *testing.T) {
|
func TestRouteUpdate(t *testing.T) {
|
||||||
// Create a test logger
|
// Create a test logger
|
||||||
logger := slog.Default()
|
logger := logger.New()
|
||||||
|
|
||||||
// Create test config with empty state dir (no snapshot loading)
|
// Create test config with empty state dir (no snapshot loading)
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
|
@ -4,12 +4,12 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
"git.eeqj.de/sneak/routewatch/internal/routingtable"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/templates"
|
"git.eeqj.de/sneak/routewatch/internal/templates"
|
||||||
@ -23,12 +23,12 @@ type Server struct {
|
|||||||
db database.Store
|
db database.Store
|
||||||
routingTable *routingtable.RoutingTable
|
routingTable *routingtable.RoutingTable
|
||||||
streamer *streamer.Streamer
|
streamer *streamer.Streamer
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new HTTP server
|
// New creates a new HTTP server
|
||||||
func New(db database.Store, rt *routingtable.RoutingTable, streamer *streamer.Streamer, logger *slog.Logger) *Server {
|
func New(db database.Store, rt *routingtable.RoutingTable, streamer *streamer.Streamer, logger *logger.Logger) *Server {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
db: db,
|
db: db,
|
||||||
routingTable: rt,
|
routingTable: rt,
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
@ -27,7 +27,7 @@ const (
|
|||||||
type Snapshotter struct {
|
type Snapshotter struct {
|
||||||
rt *routingtable.RoutingTable
|
rt *routingtable.RoutingTable
|
||||||
stateDir string
|
stateDir string
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
mu sync.Mutex // Ensures only one snapshot runs at a time
|
mu sync.Mutex // Ensures only one snapshot runs at a time
|
||||||
@ -36,7 +36,7 @@ type Snapshotter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Snapshotter instance
|
// New creates a new Snapshotter instance
|
||||||
func New(rt *routingtable.RoutingTable, cfg *config.Config, logger *slog.Logger) (*Snapshotter, error) {
|
func New(rt *routingtable.RoutingTable, cfg *config.Config, logger *logger.Logger) (*Snapshotter, error) {
|
||||||
stateDir := cfg.GetStateDir()
|
stateDir := cfg.GetStateDir()
|
||||||
|
|
||||||
// If state directory is specified, ensure it exists
|
// If state directory is specified, ensure it exists
|
||||||
|
@ -7,13 +7,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
||||||
)
|
)
|
||||||
@ -63,7 +63,7 @@ type handlerInfo struct {
|
|||||||
|
|
||||||
// Streamer handles streaming BGP updates from RIS Live
|
// Streamer handles streaming BGP updates from RIS Live
|
||||||
type Streamer struct {
|
type Streamer struct {
|
||||||
logger *slog.Logger
|
logger *logger.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
handlers []*handlerInfo
|
handlers []*handlerInfo
|
||||||
rawHandler RawMessageHandler
|
rawHandler RawMessageHandler
|
||||||
@ -75,7 +75,7 @@ type Streamer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new RIS streamer
|
// New creates a new RIS streamer
|
||||||
func New(logger *slog.Logger, metrics *metrics.Tracker) *Streamer {
|
func New(logger *logger.Logger, metrics *metrics.Tracker) *Streamer {
|
||||||
return &Streamer{
|
return &Streamer{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
|
@ -3,12 +3,12 @@ package streamer
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||||
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
||||||
"log/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewStreamer(t *testing.T) {
|
func TestNewStreamer(t *testing.T) {
|
||||||
logger := slog.Default()
|
logger := logger.New()
|
||||||
metricsTracker := metrics.New()
|
metricsTracker := metrics.New()
|
||||||
s := New(logger, metricsTracker)
|
s := New(logger, metricsTracker)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user