gitea, notion webhook
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

94 lines
2.8 KiB

// Package logging configures the application's structured logger (log/slog)
// based on the environment: human-readable text in development, JSON in production.
package logging
import (
"io"
"log/slog"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"git/palnet/slack-notifier/internal/config"
)
// Setup builds the slog logger from config and installs it as the default
// (so package-level slog.Info/Warn/Error calls everywhere use this config).
//
// poster가 nil이 아니고 cfg.MonitorChannel이 설정돼 있으면, Error 이상 레벨의 로그를
// 해당 Slack 채널로도 알린다. (poster는 보통 *slack.Client)
func Setup(cfg config.Config, poster AlertPoster) *slog.Logger {
w := resolveWriter(cfg)
opts := &slog.HandlerOptions{Level: parseLevel(cfg.LogLevel)}
var h slog.Handler
if strings.EqualFold(cfg.LogFormat, "json") {
h = slog.NewJSONHandler(w, opts)
} else {
h = slog.NewTextHandler(w, opts)
}
if poster != nil && cfg.MonitorChannel != "" {
h = newSlackAlertHandler(h, poster, cfg.MonitorChannel)
}
logger := slog.New(h).With("service", "slack-notifier", "env", cfg.Env)
slog.SetDefault(logger)
return logger
}
// resolveWriter returns stdout (development), or stdout+날짜별 파일 when LOG_FILE is set
// (production defaults to logs/app.log → logs/app-YYYY-MM-DD.log). 날짜가 바뀌면 새 파일로
// 회전하고 LOG_RETENTION_DAYS일치만 보존한다. stdout도 유지해 Docker/journald가 함께 캡처.
func resolveWriter(cfg config.Config) io.Writer {
if cfg.LogFile == "" {
return os.Stdout
}
dw, err := newDailyWriter(cfg.LogFile, cfg.LogRetentionDays)
if err != nil {
// 파일 준비 실패 — stdout으로 폴백(기본 로거로 경고).
slog.Warn("cannot open log file, using stdout only", "file", cfg.LogFile, "err", err)
return os.Stdout
}
return io.MultiWriter(os.Stdout, dw)
}
func parseLevel(s string) slog.Level {
switch strings.ToLower(s) {
case "debug":
return slog.LevelDebug
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
// GinMiddleware logs each HTTP request through slog, replacing gin.Logger()
// so access logs share the same env-based format.
func GinMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
level := slog.LevelInfo
if c.Writer.Status() >= 500 {
level = slog.LevelError
} else if c.Writer.Status() >= 400 {
level = slog.LevelWarn
}
slog.LogAttrs(c.Request.Context(), level, "http_request",
slog.String("method", c.Request.Method),
slog.String("path", c.Request.URL.Path),
slog.Int("status", c.Writer.Status()),
slog.Int64("latency_ms", time.Since(start).Milliseconds()),
slog.String("client_ip", c.ClientIP()),
)
}
}