// Package log предоставляет структурированное логирование на основе slog. // Поддерживает JSON-формат для production и текстовый для development. // Позволяет получать/сохранять логгер в контексте (FromContext/WithContext), // добавлять request_id, user_id, error и произвольные поля к записям. package log import ( "context" "log/slog" "os" "runtime" "strconv" ) type ctxKey string const loggerKey ctxKey = "logger" var defaultLogger *slog.Logger func Init(env string) *slog.Logger { var handler slog.Handler if env == "production" { handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, AddSource: true, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { return slog.Attr{Key: "timestamp", Value: a.Value} } if a.Key == slog.LevelKey { return slog.Attr{Key: "level", Value: a.Value} } if a.Key == slog.MessageKey { return slog.Attr{Key: "message", Value: a.Value} } return a }, }) } else { handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelDebug, AddSource: true, }) } defaultLogger = slog.New(handler) slog.SetDefault(defaultLogger) return defaultLogger } func Default() *slog.Logger { if defaultLogger == nil { return slog.Default() } return defaultLogger } func FromContext(ctx context.Context) *slog.Logger { if logger, ok := ctx.Value(loggerKey).(*slog.Logger); ok { return logger } return Default() } func WithContext(ctx context.Context, logger *slog.Logger) context.Context { return context.WithValue(ctx, loggerKey, logger) } func WithRequestID(ctx context.Context, requestID string) *slog.Logger { return FromContext(ctx).With("request_id", requestID) } func WithUserID(ctx context.Context, userID string) *slog.Logger { return FromContext(ctx).With("user_id", userID) } func WithError(err error) slog.Attr { return slog.String("error", err.Error()) } func WithFields(fields map[string]any) []slog.Attr { attrs := make([]slog.Attr, 0, len(fields)) for k, v := range fields { attrs = append(attrs, slog.Any(k, v)) } return attrs } func LogError(ctx context.Context, msg string, err error, args ...any) { logger := FromContext(ctx) allArgs := append([]any{"error", err}, args...) logger.ErrorContext(ctx, msg, allArgs...) } func LogWarn(ctx context.Context, msg string, args ...any) { FromContext(ctx).WarnContext(ctx, msg, args...) } func LogInfo(ctx context.Context, msg string, args ...any) { FromContext(ctx).InfoContext(ctx, msg, args...) } func LogDebug(ctx context.Context, msg string, args ...any) { FromContext(ctx).DebugContext(ctx, msg, args...) } func callerInfo(skip int) slog.Attr { _, file, line, ok := runtime.Caller(skip + 1) if !ok { return slog.String("caller", "unknown") } return slog.String("caller", file+":"+strconv.Itoa(line)) }