163 lines
3.7 KiB
Go
163 lines
3.7 KiB
Go
package logfile
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// Level is record severity. LevelTrace..LevelError are ordered for threshold
|
|
// filtering; LevelProc and LevelBad sit outside that order.
|
|
type Level uint8
|
|
|
|
const (
|
|
LevelTrace Level = iota
|
|
LevelDebug
|
|
LevelInfo
|
|
LevelWarn
|
|
LevelError
|
|
LevelProc
|
|
LevelBad
|
|
LevelCount
|
|
)
|
|
|
|
// OrderedCount is the number of threshold-comparable levels.
|
|
const OrderedCount = int(LevelError) + 1
|
|
|
|
var levelName = [LevelCount]string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "PROC", "BAD"}
|
|
var levelInitial = [LevelCount]byte{'T', 'D', 'I', 'W', 'E', 'P', 'B'}
|
|
|
|
func (l Level) String() string {
|
|
if l < LevelCount {
|
|
return levelName[l]
|
|
}
|
|
return "?"
|
|
}
|
|
|
|
// Initial returns the single character used to toggle the level.
|
|
func (l Level) Initial() byte {
|
|
if l < LevelCount {
|
|
return levelInitial[l]
|
|
}
|
|
return '?'
|
|
}
|
|
|
|
// ParseLevel maps a level token to a Level; unknown tokens are LevelBad.
|
|
func ParseLevel(b []byte) Level {
|
|
switch string(b) {
|
|
case "TRACE":
|
|
return LevelTrace
|
|
case "DEBUG":
|
|
return LevelDebug
|
|
case "INFO":
|
|
return LevelInfo
|
|
case "WARN":
|
|
return LevelWarn
|
|
case "ERROR":
|
|
return LevelError
|
|
case "PROC":
|
|
return LevelProc
|
|
}
|
|
return LevelBad
|
|
}
|
|
|
|
// LevelByInitial resolves a toggle character to a Level.
|
|
func LevelByInitial(c byte) (Level, bool) {
|
|
for i := Level(0); i < LevelCount; i++ {
|
|
if levelInitial[i] == c {
|
|
return i, true
|
|
}
|
|
}
|
|
return LevelBad, false
|
|
}
|
|
|
|
// SubStat marks stat snapshot records.
|
|
const SubStat = "stat"
|
|
|
|
// StampText renders a record's wall-clock stamp, or a placeholder when the
|
|
// line carried no usable time and inherited one for ordering.
|
|
func StampText(m Meta) string {
|
|
if m.Flags&FlagNoTime != 0 {
|
|
return "--:--:--.---"
|
|
}
|
|
return FormatTS(m.TS)
|
|
}
|
|
|
|
// KnownSubs is the closed subsystem vocabulary; ad-hoc taps are also accepted.
|
|
var KnownSubs = []string{
|
|
"app", "service", "fsm", "event", "dispatch", "push",
|
|
"input", "stat", "rec", "lock", "race", "crash",
|
|
}
|
|
|
|
// DurUnit is the time unit implied by a metric key's suffix.
|
|
type DurUnit uint8
|
|
|
|
const (
|
|
DurNone DurUnit = iota
|
|
DurNs
|
|
DurUs
|
|
DurMs
|
|
)
|
|
|
|
// durKeys maps a key name to the unit it implies. Order matters: the first
|
|
// match wins, so unit suffixes are tested after the bare duration names.
|
|
var durKeys = []struct {
|
|
name string
|
|
unit DurUnit
|
|
}{
|
|
{"timer", DurNs}, {"duration", DurNs}, {"elapsed", DurNs}, {"remaining", DurNs},
|
|
{"ns", DurNs}, {"us", DurUs}, {"ms", DurMs},
|
|
}
|
|
|
|
// DurationUnit reports the duration unit implied by a field key.
|
|
func DurationUnit(key string) DurUnit {
|
|
for _, d := range durKeys {
|
|
if hasKeySegment(key, d.name) {
|
|
return d.unit
|
|
}
|
|
}
|
|
return DurNone
|
|
}
|
|
|
|
// hasKeySegment matches name as the whole key or as its trailing '.'/'_'
|
|
// segment: "elapsed", "max_duration" and "fsm.elapsed" match, "populations"
|
|
// does not match "ns".
|
|
func hasKeySegment(key, name string) bool {
|
|
if len(key) < len(name) || key[len(key)-len(name):] != name {
|
|
return false
|
|
}
|
|
if len(key) == len(name) {
|
|
return true
|
|
}
|
|
c := key[len(key)-len(name)-1]
|
|
return c == '.' || c == '_'
|
|
}
|
|
|
|
// FormatDuration renders v, expressed in unit u, as a human duration.
|
|
func FormatDuration(v int64, u DurUnit) string {
|
|
switch u {
|
|
case DurNs:
|
|
return time.Duration(v).String()
|
|
case DurUs:
|
|
return (time.Duration(v) * time.Microsecond).String()
|
|
case DurMs:
|
|
return (time.Duration(v) * time.Millisecond).String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// FormatTS renders unix nanoseconds as a local wall-clock stamp.
|
|
func FormatTS(ns int64) string {
|
|
if ns == 0 {
|
|
return "--:--:--.---"
|
|
}
|
|
return time.Unix(0, ns).Format("15:04:05.000")
|
|
}
|
|
|
|
// Dash renders empty vocabulary values: records without sub, or without a
|
|
// fields.msg/type discriminator.
|
|
func Dash(s string) string {
|
|
if s == "" {
|
|
return "-"
|
|
}
|
|
return s
|
|
}
|