feat: add Logbull remote logging via slog handler, tee to stdout
This commit is contained in:
+92
-4
@@ -18,19 +18,66 @@ import (
|
||||
"aiaa-notification-service/internal/store"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/logbull/logbull-go/logbull"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
slog.SetDefault(logger)
|
||||
|
||||
// Load config
|
||||
// Load config first (need logbull settings for logger setup)
|
||||
cfg, err := config.Load("config/config.yaml")
|
||||
if err != nil {
|
||||
slog.Error("failed to load config", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup dual logger: stdout JSON + Logbull remote
|
||||
stdoutHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
|
||||
|
||||
var logbullHandler slog.Handler
|
||||
if cfg.Logbull.Host != "" {
|
||||
lbCfg := logbull.Config{
|
||||
Host: cfg.Logbull.Host,
|
||||
ProjectID: cfg.Logbull.ProjectID,
|
||||
APIKey: cfg.Logbull.APIKey,
|
||||
}
|
||||
switch cfg.Logbull.LogLevel {
|
||||
case "DEBUG":
|
||||
lbCfg.LogLevel = logbull.DEBUG
|
||||
case "WARNING":
|
||||
lbCfg.LogLevel = logbull.WARNING
|
||||
case "ERROR":
|
||||
lbCfg.LogLevel = logbull.ERROR
|
||||
default:
|
||||
lbCfg.LogLevel = logbull.INFO
|
||||
}
|
||||
h, err := logbull.NewSlogHandler(lbCfg)
|
||||
if err != nil {
|
||||
slog.Warn("logbull init failed, using stdout only", "error", err)
|
||||
} else {
|
||||
logbullHandler = h
|
||||
}
|
||||
}
|
||||
|
||||
var baseHandler slog.Handler
|
||||
if logbullHandler != nil {
|
||||
baseHandler = &teeHandler{handlers: []slog.Handler{stdoutHandler, logbullHandler}}
|
||||
} else {
|
||||
baseHandler = stdoutHandler
|
||||
}
|
||||
|
||||
logger := slog.New(baseHandler)
|
||||
slog.SetDefault(logger)
|
||||
|
||||
// Flush logbull on exit
|
||||
defer func() {
|
||||
if lbh, ok := logbullHandler.(interface{ Flush() }); ok {
|
||||
lbh.Flush()
|
||||
}
|
||||
if lbh, ok := logbullHandler.(interface{ Shutdown() }); ok {
|
||||
lbh.Shutdown()
|
||||
}
|
||||
time.Sleep(2 * time.Second) // allow async flush
|
||||
}()
|
||||
|
||||
// Connect MySQL
|
||||
st, err := store.NewStore(cfg.Database)
|
||||
if err != nil {
|
||||
@@ -158,3 +205,44 @@ func main() {
|
||||
}
|
||||
slog.Info("server stopped")
|
||||
}
|
||||
|
||||
// teeHandler fans out log records to multiple handlers (stdout + remote).
|
||||
type teeHandler struct {
|
||||
handlers []slog.Handler
|
||||
}
|
||||
|
||||
func (t *teeHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
for _, h := range t.handlers {
|
||||
if h.Enabled(ctx, level) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *teeHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
for _, h := range t.handlers {
|
||||
if h.Enabled(ctx, r.Level) {
|
||||
if err := h.Handle(ctx, r.Clone()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *teeHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
handlers := make([]slog.Handler, len(t.handlers))
|
||||
for i, h := range t.handlers {
|
||||
handlers[i] = h.WithAttrs(attrs)
|
||||
}
|
||||
return &teeHandler{handlers: handlers}
|
||||
}
|
||||
|
||||
func (t *teeHandler) WithGroup(name string) slog.Handler {
|
||||
handlers := make([]slog.Handler, len(t.handlers))
|
||||
for i, h := range t.handlers {
|
||||
handlers[i] = h.WithGroup(name)
|
||||
}
|
||||
return &teeHandler{handlers: handlers}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user