Use ISO 8601 format for server logging
This commit is contained in:
BIN
Binary file not shown.
+46
-36
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/igodwin/notifier/api/rest"
|
"github.com/igodwin/notifier/api/rest"
|
||||||
"github.com/igodwin/notifier/internal/config"
|
"github.com/igodwin/notifier/internal/config"
|
||||||
"github.com/igodwin/notifier/internal/domain"
|
"github.com/igodwin/notifier/internal/domain"
|
||||||
|
"github.com/igodwin/notifier/internal/logging"
|
||||||
"github.com/igodwin/notifier/internal/notifier"
|
"github.com/igodwin/notifier/internal/notifier"
|
||||||
"github.com/igodwin/notifier/internal/queue"
|
"github.com/igodwin/notifier/internal/queue"
|
||||||
"github.com/igodwin/notifier/internal/service"
|
"github.com/igodwin/notifier/internal/service"
|
||||||
@@ -26,11 +26,21 @@ func main() {
|
|||||||
// Load configuration
|
// Load configuration
|
||||||
cfg, err := config.Load("")
|
cfg, err := config.Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: failed to load config, using defaults: %v", err)
|
// Use basic logger before we have config
|
||||||
|
logger, _ := logging.NewFromConfig("info", "stdout")
|
||||||
|
logger.Warnf("Failed to load config, using defaults: %v", err)
|
||||||
cfg = getDefaultConfig()
|
cfg = getDefaultConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Starting Notifier Service in mode: %s", cfg.Server.Mode)
|
// Create logger from config
|
||||||
|
logger, err := logging.NewFromConfig(cfg.Logging.Level, cfg.Logging.OutputPath)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to stdout if log file can't be opened
|
||||||
|
logger, _ = logging.NewFromConfig(cfg.Logging.Level, "stdout")
|
||||||
|
logger.Warnf("Failed to open log file, using stdout: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Starting Notifier Service in mode: %s", cfg.Server.Mode)
|
||||||
|
|
||||||
// Create context
|
// Create context
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -41,32 +51,32 @@ func main() {
|
|||||||
if cfg.Queue.Type == "local" {
|
if cfg.Queue.Type == "local" {
|
||||||
q, err = queue.NewLocalQueue(cfg.Queue.Local)
|
q, err = queue.NewLocalQueue(cfg.Queue.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create queue: %v", err)
|
logger.Fatalf("Failed to create queue: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Using local queue")
|
logger.Info("Using local queue")
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Queue type %s not implemented yet", cfg.Queue.Type)
|
logger.Fatalf("Queue type %s not implemented yet", cfg.Queue.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize notifier factory and register notifiers
|
// Initialize notifier factory and register notifiers
|
||||||
factory := notifier.NewFactory()
|
factory := notifier.NewFactory()
|
||||||
registerNotifiers(cfg, factory)
|
registerNotifiers(cfg, factory, logger)
|
||||||
|
|
||||||
// Check if any notifiers are registered
|
// Check if any notifiers are registered
|
||||||
if len(factory.SupportedTypes()) == 0 {
|
if len(factory.SupportedTypes()) == 0 {
|
||||||
log.Fatal("No notifiers configured. Please enable at least one notifier in config.yaml")
|
logger.Fatal("No notifiers configured. Please enable at least one notifier in config.yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Supported notification types: %v", factory.SupportedTypes())
|
logger.Infof("Supported notification types: %v", factory.SupportedTypes())
|
||||||
|
|
||||||
// Create notification service
|
// Create notification service
|
||||||
svc := service.NewNotificationService(factory, q, cfg.Queue.WorkerCount)
|
svc := service.NewNotificationService(factory, q, cfg.Queue.WorkerCount)
|
||||||
|
|
||||||
// Start workers
|
// Start workers
|
||||||
if err := svc.Start(ctx); err != nil {
|
if err := svc.Start(ctx); err != nil {
|
||||||
log.Fatalf("Failed to start service: %v", err)
|
logger.Fatalf("Failed to start service: %v", err)
|
||||||
}
|
}
|
||||||
log.Printf("Started %d worker(s)", cfg.Queue.WorkerCount)
|
logger.Infof("Started %d worker(s)", cfg.Queue.WorkerCount)
|
||||||
|
|
||||||
// Wait group for both servers
|
// Wait group for both servers
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -75,14 +85,14 @@ func main() {
|
|||||||
var grpcServer *grpc.Server
|
var grpcServer *grpc.Server
|
||||||
if cfg.Server.Mode == "both" || cfg.Server.Mode == "grpc" {
|
if cfg.Server.Mode == "both" || cfg.Server.Mode == "grpc" {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
grpcServer = startGRPCServer(ctx, &wg, cfg, svc)
|
grpcServer = startGRPCServer(ctx, &wg, cfg, svc, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start REST server if enabled
|
// Start REST server if enabled
|
||||||
var restServer *http.Server
|
var restServer *http.Server
|
||||||
if cfg.Server.Mode == "both" || cfg.Server.Mode == "rest" {
|
if cfg.Server.Mode == "both" || cfg.Server.Mode == "rest" {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
restServer = startRESTServer(ctx, &wg, cfg, svc)
|
restServer = startRESTServer(ctx, &wg, cfg, svc, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for interrupt signal
|
// Wait for interrupt signal
|
||||||
@@ -90,7 +100,7 @@ func main() {
|
|||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||||
<-sigChan
|
<-sigChan
|
||||||
|
|
||||||
log.Println("Shutting down servers...")
|
logger.Info("Shutting down servers...")
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
@@ -99,7 +109,7 @@ func main() {
|
|||||||
// Stop REST server
|
// Stop REST server
|
||||||
if restServer != nil {
|
if restServer != nil {
|
||||||
if err := restServer.Shutdown(shutdownCtx); err != nil {
|
if err := restServer.Shutdown(shutdownCtx); err != nil {
|
||||||
log.Printf("Error during REST server shutdown: %v", err)
|
logger.Errorf("Error during REST server shutdown: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,64 +123,64 @@ func main() {
|
|||||||
|
|
||||||
// Stop service
|
// Stop service
|
||||||
if err := svc.Stop(); err != nil {
|
if err := svc.Stop(); err != nil {
|
||||||
log.Printf("Error stopping service: %v", err)
|
logger.Errorf("Error stopping service: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Servers stopped")
|
logger.Info("Servers stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerNotifiers(cfg *config.Config, factory *notifier.Factory) {
|
func registerNotifiers(cfg *config.Config, factory *notifier.Factory, logger *logging.Logger) {
|
||||||
if cfg.Notifiers.Stdout {
|
if cfg.Notifiers.Stdout {
|
||||||
stdoutNotifier := notifier.NewStdoutNotifier()
|
stdoutNotifier := notifier.NewStdoutNotifier()
|
||||||
if err := factory.RegisterNotifier(domain.TypeStdout, stdoutNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeStdout, stdoutNotifier); err != nil {
|
||||||
log.Fatalf("Failed to register stdout notifier: %v", err)
|
logger.Fatalf("Failed to register stdout notifier: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Registered stdout notifier")
|
logger.Info("Registered stdout notifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.SMTP != nil {
|
if cfg.Notifiers.SMTP != nil {
|
||||||
smtpNotifier, err := notifier.NewSMTPNotifier(cfg.Notifiers.SMTP)
|
smtpNotifier, err := notifier.NewSMTPNotifier(cfg.Notifiers.SMTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: failed to create SMTP notifier: %v", err)
|
logger.Warnf("Failed to create SMTP notifier: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeEmail, smtpNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeEmail, smtpNotifier); err != nil {
|
||||||
log.Fatalf("Failed to register SMTP notifier: %v", err)
|
logger.Fatalf("Failed to register SMTP notifier: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Registered SMTP notifier")
|
logger.Info("Registered SMTP notifier")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.Slack != nil {
|
if cfg.Notifiers.Slack != nil {
|
||||||
slackNotifier, err := notifier.NewSlackNotifier(cfg.Notifiers.Slack)
|
slackNotifier, err := notifier.NewSlackNotifier(cfg.Notifiers.Slack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: failed to create Slack notifier: %v", err)
|
logger.Warnf("Failed to create Slack notifier: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeSlack, slackNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeSlack, slackNotifier); err != nil {
|
||||||
log.Fatalf("Failed to register Slack notifier: %v", err)
|
logger.Fatalf("Failed to register Slack notifier: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Registered Slack notifier")
|
logger.Info("Registered Slack notifier")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.Ntfy != nil {
|
if cfg.Notifiers.Ntfy != nil {
|
||||||
ntfyNotifier, err := notifier.NewNtfyNotifier(cfg.Notifiers.Ntfy)
|
ntfyNotifier, err := notifier.NewNtfyNotifier(cfg.Notifiers.Ntfy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: failed to create Ntfy notifier: %v", err)
|
logger.Warnf("Failed to create Ntfy notifier: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeNtfy, ntfyNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeNtfy, ntfyNotifier); err != nil {
|
||||||
log.Fatalf("Failed to register Ntfy notifier: %v", err)
|
logger.Fatalf("Failed to register Ntfy notifier: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Registered Ntfy notifier")
|
logger.Info("Registered Ntfy notifier")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService) *grpc.Server {
|
func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger) *grpc.Server {
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.GRPCPort)
|
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.GRPCPort)
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", addr)
|
lis, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to listen on %s: %v", addr, err)
|
logger.Fatalf("Failed to listen on %s: %v", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
grpcServer := grpc.NewServer()
|
grpcServer := grpc.NewServer()
|
||||||
@@ -183,16 +193,16 @@ func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
log.Printf("gRPC server listening on %s", addr)
|
logger.Infof("gRPC server listening on %s", addr)
|
||||||
if err := grpcServer.Serve(lis); err != nil {
|
if err := grpcServer.Serve(lis); err != nil {
|
||||||
log.Fatalf("Failed to serve gRPC: %v", err)
|
logger.Fatalf("Failed to serve gRPC: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return grpcServer
|
return grpcServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService) *http.Server {
|
func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger) *http.Server {
|
||||||
router := rest.NewRouter(svc)
|
router := rest.NewRouter(svc)
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.RESTPort)
|
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.RESTPort)
|
||||||
@@ -206,9 +216,9 @@ func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
log.Printf("REST server listening on %s", addr)
|
logger.Infof("REST server listening on %s", addr)
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("Failed to start REST server: %v", err)
|
logger.Fatalf("Failed to start REST server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger provides structured logging with ISO 8601 timestamps
|
||||||
|
type Logger struct {
|
||||||
|
*log.Logger
|
||||||
|
level LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevel represents the logging level
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DebugLevel LogLevel = iota
|
||||||
|
InfoLevel
|
||||||
|
WarnLevel
|
||||||
|
ErrorLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new logger with ISO 8601 timestamp format
|
||||||
|
func New(level LogLevel, output io.Writer) *Logger {
|
||||||
|
if output == nil {
|
||||||
|
output = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Logger{
|
||||||
|
Logger: log.New(output, "", 0), // No flags, we'll format ourselves
|
||||||
|
level: level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromConfig creates a logger from configuration
|
||||||
|
func NewFromConfig(levelStr string, outputPath string) (*Logger, error) {
|
||||||
|
level := parseLevel(levelStr)
|
||||||
|
|
||||||
|
var output io.Writer
|
||||||
|
switch outputPath {
|
||||||
|
case "stdout", "":
|
||||||
|
output = os.Stdout
|
||||||
|
case "stderr":
|
||||||
|
output = os.Stderr
|
||||||
|
default:
|
||||||
|
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
output = file
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(level, output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMessage formats a log message with ISO 8601 timestamp
|
||||||
|
func (l *Logger) formatMessage(level string, msg string) string {
|
||||||
|
timestamp := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
return timestamp + " [" + level + "] " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a debug message
|
||||||
|
func (l *Logger) Debug(msg string) {
|
||||||
|
if l.level <= DebugLevel {
|
||||||
|
l.Logger.Println(l.formatMessage("DEBUG", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a formatted debug message
|
||||||
|
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
if l.level <= DebugLevel {
|
||||||
|
msg := sprintf(format, args...)
|
||||||
|
l.Logger.Println(l.formatMessage("DEBUG", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs an info message
|
||||||
|
func (l *Logger) Info(msg string) {
|
||||||
|
if l.level <= InfoLevel {
|
||||||
|
l.Logger.Println(l.formatMessage("INFO", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted info message
|
||||||
|
func (l *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
if l.level <= InfoLevel {
|
||||||
|
msg := sprintf(format, args...)
|
||||||
|
l.Logger.Println(l.formatMessage("INFO", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a warning message
|
||||||
|
func (l *Logger) Warn(msg string) {
|
||||||
|
if l.level <= WarnLevel {
|
||||||
|
l.Logger.Println(l.formatMessage("WARN", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a formatted warning message
|
||||||
|
func (l *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
if l.level <= WarnLevel {
|
||||||
|
msg := sprintf(format, args...)
|
||||||
|
l.Logger.Println(l.formatMessage("WARN", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error message
|
||||||
|
func (l *Logger) Error(msg string) {
|
||||||
|
if l.level <= ErrorLevel {
|
||||||
|
l.Logger.Println(l.formatMessage("ERROR", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a formatted error message
|
||||||
|
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
if l.level <= ErrorLevel {
|
||||||
|
msg := sprintf(format, args...)
|
||||||
|
l.Logger.Println(l.formatMessage("ERROR", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a fatal message and exits
|
||||||
|
func (l *Logger) Fatal(msg string) {
|
||||||
|
l.Logger.Println(l.formatMessage("FATAL", msg))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a formatted fatal message and exits
|
||||||
|
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
msg := sprintf(format, args...)
|
||||||
|
l.Logger.Println(l.formatMessage("FATAL", msg))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLevel parses a log level string
|
||||||
|
func parseLevel(levelStr string) LogLevel {
|
||||||
|
switch levelStr {
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel
|
||||||
|
case "info":
|
||||||
|
return InfoLevel
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel
|
||||||
|
default:
|
||||||
|
return InfoLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sprintf is a helper using fmt
|
||||||
|
func sprintf(format string, args ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, args...)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user