bldr/util/logfile/spec.go
Rama master · réplica de lectura del repositorio original.
package logfile
import (
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ErrDisabled is returned by ParseLogFileSpec when the spec is "none".
var ErrDisabled = errors.New("log file disabled")
// LogFileSpec describes a log file destination.
type LogFileSpec struct {
// Level is the logrus level threshold.
Level logrus.Level
// Format is the output format ("text" or "json").
Format string
// Path is the expanded file path (templates already resolved).
Path string
}
// ParseLogFileSpec parses a log file spec string into a LogFileSpec.
//
// Spec syntax (semicolon-delimited key=value pairs):
// - Full form: level=DEBUG;format=json;path=./.bldr/logs/{ts}.log
// - Short form: ./.bldr/logs/{ts}.log (path only, defaults apply)
// - "none" -> returns ErrDisabled
//
// Default level: DEBUG. Default format: text.
// Unknown keys produce an error. Missing path produces an error.
func ParseLogFileSpec(spec string, ts time.Time) (LogFileSpec, error) {
// Reject empty or explicitly disabled log destinations.
spec = strings.TrimSpace(spec)
if spec == "" {
return LogFileSpec{}, errors.New("empty log file spec")
}
if spec == "none" {
return LogFileSpec{}, ErrDisabled
}
// Prepare the default level and format for the log destination.
result := LogFileSpec{
Level: logrus.DebugLevel,
Format: "text",
}
// Check if this is a short form (no semicolons and no '=' sign).
if !strings.Contains(spec, ";") && !strings.Contains(spec, "=") {
result.Path = ExpandTemplate(spec, ts)
return result, nil
}
// Parse the configured fields into the log destination.
fields := strings.Split(spec, ";")
for i, field := range fields {
// Ignore blank fields in the log specification.
field = strings.TrimSpace(field)
if field == "" {
continue
}
// Separate each field value or accept a trailing bare path.
before, after, ok := strings.Cut(field, "=")
if !ok {
// Last field without '=' is treated as path.
if i == len(fields)-1 {
result.Path = ExpandTemplate(field, ts)
continue
}
return LogFileSpec{}, errors.New("invalid field " + strconv.Quote(field) + ": missing '='")
}
// Normalize the log field key and value.
key := strings.TrimSpace(before)
val := strings.TrimSpace(after)
// Apply the requested level, format, or expanded path.
switch key {
case "level":
lvl, err := logrus.ParseLevel(val)
if err != nil {
return LogFileSpec{}, errors.Wrap(err, "invalid level "+strconv.Quote(val))
}
result.Level = lvl
case "format":
if val != "text" && val != "json" {
return LogFileSpec{}, errors.New("invalid format " + strconv.Quote(val) + ": must be \"text\" or \"json\"")
}
result.Format = val
case "path":
result.Path = ExpandTemplate(val, ts)
default:
return LogFileSpec{}, errors.New("unknown key " + strconv.Quote(key))
}
}
// Require a path before returning the log destination.
if result.Path == "" {
return LogFileSpec{}, errors.New("missing path in log file spec")
}
return result, nil
}