internal/encoder/format.go

package encoder

import (
	"strconv"
	"time"
)

// FormatTimestamp returns an RFC3339-nano string in UTC. Exported so tests
// and alternative encoders share the same wire format. Zero times become
// empty strings; the JSON encoder substitutes the current time in that
// case.
func FormatTimestamp(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.UTC().Format(time.RFC3339Nano)
}

// FormatDuration returns d as a string of milliseconds with an "ms" suffix.
// Negative durations are preserved verbatim so callers can detect clock
// skew in downstream dashboards.
func FormatDuration(d time.Duration) string {
	ms := int64(d / time.Millisecond)
	return strconv.FormatInt(ms, 10) + "ms"
}

// FormatMilliseconds returns d as a bare millisecond integer.
func FormatMilliseconds(d time.Duration) int64 {
	return int64(d / time.Millisecond)
}

// FormatLevel returns the canonical lowercase representation of the level
// name. Unknown inputs pass through lowercased so user code is not
// prevented from inventing its own levels (e.g. "audit").
func FormatLevel(level string) string {
	switch level {
	case "TRACE", "trace":
		return "trace"
	case "DEBUG", "debug":
		return "debug"
	case "INFO", "info":
		return "info"
	case "WARN", "warning", "WARNING":
		return "warn"
	case "ERROR", "error":
		return "error"
	case "FATAL", "fatal":
		return "fatal"
	}
	return toLower(level)
}

// toLower is an inline ASCII-only lowercaser. The log-level space never
// contains non-ASCII, so the unicode-aware path is unnecessary.
func toLower(s string) string {
	needs := false
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c >= 'A' && c <= 'Z' {
			needs = true
			break
		}
	}
	if !needs {
		return s
	}
	b := make([]byte, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c >= 'A' && c <= 'Z' {
			c += 32
		}
		b[i] = c
	}
	return string(b)
}

// LevelPriority returns a monotonically increasing integer per level. Higher
// priority means more severe. Unknown names collapse to InfoPriority so
// they are neither silenced nor flagged as critical by mistake.
func LevelPriority(level string) int {
	switch FormatLevel(level) {
	case "trace":
		return 0
	case "debug":
		return 10
	case "info":
		return 20
	case "warn":
		return 30
	case "error":
		return 40
	case "fatal":
		return 50
	}
	return 20
}

// InfoPriority is the default threshold.
const InfoPriority = 20