Ctrl K
ComunidadGuías…

bldr/util/logfile/autodefault.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package logfile

import (
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
)

// AutoDefaultEnvVar is the environment variable that controls the file
// logging spec. When set to a non-empty value, including "none", the user's
// configuration wins and BuildAutoDefaultSpec returns no spec.
const AutoDefaultEnvVar = "BLDR_LOG_FILE"

// DefaultRetention is the on-disk retention duration applied when no
// project-scoped override resolves to a positive duration.
const DefaultRetention = 7 * 24 * time.Hour

// BuildAutoDefaultSpec returns the auto-default LogFileSpec for an
// entrypoint that has no explicit BLDR_LOG_FILE configuration.
//
// The resolved spec writes DEBUG-level text records to
// <storageRoot>/logs/{ts}.log, where {ts} is expanded against now.
// If BLDR_LOG_FILE is set to a non-empty value (including "none"), the user's
// configuration takes precedence and this function returns ok=false so the
// caller defers to the existing parsing path. Empty values behave like the
// variable is unset and still get the auto-default.
//
// Callers receive the spec but are responsible for opening the file via
// AttachLogFiles and pruning old logs via PruneOldLogs.
func BuildAutoDefaultSpec(storageRoot string, now time.Time) (LogFileSpec, bool) {
	if raw, ok := os.LookupEnv(AutoDefaultEnvVar); ok && strings.TrimSpace(raw) != "" {
		return LogFileSpec{}, false
	}
	if storageRoot == "" {
		return LogFileSpec{}, false
	}
	path := filepath.Join(storageRoot, "logs", "{ts}.log")
	return LogFileSpec{
		Level:  logrus.DebugLevel,
		Format: "text",
		Path:   ExpandTemplate(path, now),
	}, true
}

// ResolveRetention returns the configured on-disk log retention duration.
// It reads the project-scoped retention env var (e.g.
// SPACEWAVE_LOG_RETENTION_DAYS) and falls back to DefaultRetention when
// the variable is unset, blank, non-positive, or unparseable as a
// non-negative integer.
//
// When the variable is set but cannot be parsed, the returned warn message
// is non-empty so callers can emit one logrus.Warn at startup. A
// successful parse always returns warn == "".
func ResolveRetention(env, raw string) (dur time.Duration, warn string) {
	// Use the default retention when the environment value is blank.
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return DefaultRetention, ""
	}

	// Parse the retention days and report malformed configuration.
	days, err := strconv.Atoi(raw)
	if err != nil {
		return DefaultRetention, env + "=" + strconv.Quote(raw) +
			" is not a non-negative integer; using default retention"
	}

	// Keep the default retention for non-positive durations.
	if days <= 0 {
		return DefaultRetention, ""
	}

	return time.Duration(days) * 24 * time.Hour, ""
}

// ResolveLogLevel walks envChain in order, returning the first parseable
// logrus level found. Empty / unset values are skipped; a value that fails
// to parse is also skipped (callers receive fallback in that case).
// Returns fallback when no env var resolves.
func ResolveLogLevel(envChain []string, fallback logrus.Level) logrus.Level {
	for _, name := range envChain {
		raw := strings.TrimSpace(os.Getenv(name))
		if raw == "" {
			continue
		}
		lvl, err := logrus.ParseLevel(raw)
		if err != nil {
			continue
		}
		return lvl
	}
	return fallback
}

// EnableAutoDefault enables file logging at the storage-root-relative
// default location when BLDR_LOG_FILE is unset. It prunes stale logs,
// attaches the file hook, and raises the logger level so console
// filtering applies via EnsureLoggerLevel.
//
// Returns a cleanup function that closes the file hook (nil when no
// hook was attached). When BLDR_LOG_FILE is set or storageRoot is
// empty, this is a no-op and returns (nil, nil).
//
// retentionEnv names the project-scoped retention env var (e.g.
// "SPACEWAVE_LOG_RETENTION_DAYS"); pass "" to skip the override and
// always use DefaultRetention. logger receives the retention warning
// (if any) before the hook is attached so it lands in stderr but not
// in the about-to-be-created file.
func EnableAutoDefault(
	logger *logrus.Logger,
	storageRoot string,
	retentionEnv string,
	now time.Time,
) (func(), error) {
	// Resolve the default log destination unless explicit configuration takes precedence.
	spec, ok := BuildAutoDefaultSpec(storageRoot, now)
	if !ok {
		return nil, nil
	}

	// Resolve the project retention override and report its warning.
	retention := DefaultRetention
	if retentionEnv != "" {
		dur, warn := ResolveRetention(retentionEnv, os.Getenv(retentionEnv))
		retention = dur
		if warn != "" {
			logger.Warn(warn)
		}
	}

	// Prune expired logs before opening the new log file.
	logsDir := filepath.Dir(spec.Path)
	if _, err := PruneOldLogs(logsDir, retention, DefaultKeepLogs, now); err != nil {
		logger.WithError(err).Warn("failed to prune old logs")
	}

	// Attach the default file hook and enable its requested verbosity.
	cleanup, err := AttachLogFiles(logger, []LogFileSpec{spec})
	if err != nil {
		return nil, err
	}
	EnsureLoggerLevel(logger, []LogFileSpec{spec})
	return cleanup, nil
}