package c4hgol import ( "fmt" "io" "sort" "strconv" "strings" ) // Type Level denotes the importance of a log message. It sets 0 to be the // normal log message most commonly identified as Info. More important messages // have higher values and less important levels get lower values. These values // have to be mapped to specific logging packages by the respective Configurer // implementation. // // Note that there are no predefined constants for Panic and Fatal as these // also imply specific behaviour that gose beyond logging a message. For c4hgol // we consider Panic and Fatal to be "a separate story". type Level = int32 const ( LeastImportant Level = -2147483647 Trace Level = -1431655764 Debug Level = -715827882 Info Level = 0 Warn Level = 715827882 // ~ 1/3 of 2^31 Error Level = 1431655764 MostImportant Level = 2147483647 ) type Configurer interface { Name() string Enabled() bool Enable(setEnabled bool) Level() Level SetLevel(l Level) Output() io.Writer SetOutput(wr io.Writer) } type Topic struct { repr Configurer subs []Configurer } var _ Configurer = Topic{} func NewTopic(represents Configurer, subtopics ...Configurer) Configurer { res := &Topic{repr: represents, subs: subtopics} for _, sub := range subtopics { sub.SetLevel(represents.Level()) sub.Enable(represents.Enabled()) sub.SetOutput(represents.Output()) } return res } func (t Topic) Name() string { return t.repr.Name() } func (t Topic) Enabled() bool { return t.repr.Enabled() } func (t Topic) Enable(setEnabled bool) { t.repr.Enable(setEnabled) for _, sub := range t.subs { sub.Enable(setEnabled) } } // Level returns the most irrelevant level of all Configurers in this topic. // If the topic has no Configurer Info is always returned. func (t Topic) Level() (l Level) { l = t.repr.Level() for _, sub := range t.subs { if tmp := sub.Level(); tmp < l { l = tmp } } return l } func (t Topic) SetLevel(l Level) { t.repr.SetLevel(l) for _, sub := range t.subs { sub.SetLevel(l) } } func (t Topic) Output() io.Writer { return t.repr.Output() } func (t Topic) SetOutput(wr io.Writer) { t.repr.SetOutput(wr) for _, sub := range t.subs { sub.SetOutput(wr) } } func ForTopics(cfg Configurer, do func(current Configurer, path []string)) { var path []string var lsTopics func(Configurer) lsTopics = func(cfg Configurer) { // local function makes smaller stack? path = append(path, cfg.Name()) do(cfg, path) if topic, ok := cfg.(*Topic); ok { for _, sub := range topic.subs { lsTopics(sub) } } path = path[:len(path)-1] } lsTopics(cfg) } func ListTopics(cfg Configurer, wr io.Writer, prefix string) { ForTopics(cfg, func(_ Configurer, p []string) { fmt.Fprintf(wr, "%s%s\n", prefix, strings.Join(p, "/")) }) } var stdLvlMap = map[string]Level{ "t": Trace, "trace": Trace, "d": Debug, "debug": Debug, "i": Info, "info": Info, "w": Warn, "warn": Warn, "e": Error, "error": Error, } func parseLevel(lvl string, lvlMap map[string]Level) Level { if res, ok := lvlMap[lvl]; ok { return res } res, error := strconv.ParseInt(lvl, 10, 32) if error != nil { panic(fmt.Errorf("invalid debug level: '%s'", lvl)) } return Level(res) } func SetLevel(cfg Configurer, cfgStr string, lvlMap map[string]Level) { if len(cfgStr) == 0 { return } if lvlMap == nil { lvlMap = stdLvlMap } cmds := strings.Split(cfgStr, ",") for _, cmd := range cmds { if sep := strings.IndexByte(cmd, ':'); sep < 0 { lvl := parseLevel(cmd, lvlMap) cfg.SetLevel(lvl) } else if sep+1 == len(cmd) { cfg.Enable(false) } else { log := cmd[:sep] lvl := parseLevel(cmd[sep+1:], lvlMap) ForTopics(cfg, func(cfg Configurer, path []string) { if cfg.Name() == log { cfg.SetLevel(lvl) } }) } } } func LevelCfgDoc(lvlMap map[string]Level) string { if lvlMap == nil { lvlMap = stdLvlMap } var res strings.Builder res.WriteString(`set logging verbosity The standard log leves can be chosen with their name. Each name represents a numerical value. One can choose log levels in between by directly giving such a number. The standard log level are:`) type LvlsNms struct { l Level ns []string } var lvls []LvlsNms NEXT_NAME: for nm, lvl := range lvlMap { for i := range lvls { l := &lvls[i] if l.l == lvl { l.ns = append(l.ns, nm) continue NEXT_NAME } } lvls = append(lvls, LvlsNms{lvl, []string{nm}}) } sort.Slice(lvls, func(i, j int) bool { return lvls[i].l < lvls[j].l }) maxNmLen := 0 for _, lns := range lvls { sort.Slice(lns.ns, func(i, j int) bool { return len(lns.ns[i]) > len(lns.ns[j]) }) nmLen := 2 * (len(lns.ns) - 1) for _, n := range lns.ns { nmLen += len(n) // TODO better # runes } if nmLen > maxNmLen { maxNmLen = nmLen } } format := fmt.Sprintf("\n - %%-%ds (%%11d)", maxNmLen) for _, lvl := range lvls { fmt.Fprintf(&res, format, strings.Join(lvl.ns, ", "), lvl.l) } return res.String() }
package c4hgol import ( "fmt" "math" ) type LevelMap struct { Std []Level Impl []int32 } func (lm LevelMap) FromStd(level Level) int32 { return lmap(level, lm.Std, lm.Impl) } func (lm LevelMap) ToStd(level int32) Level { return lmap(level, lm.Impl, lm.Std) } // TODO error cases func lmap(lvl int32, from, to []int32) int32 { if lvl < from[0] { panic(fmt.Errorf("level %d out of range [%d … %d]", lvl, from[0], from[len(from)-1])) } end := len(from) - 1 for i := 0; i < end; i++ { fs := from[i] if fs <= lvl { fe, ts, te := from[i+1], to[i], to[i+1] return lmap1(lvl, fs, fe, ts, te) } } panic(fmt.Errorf("level %d out of range [%d … %d]", lvl, from[0], from[len(from)-1])) } func lmap1(lvl, fStart, fEnd, tStart, tEnd int32) (res int32) { switch lvl { case fStart: return tStart case fEnd: return tEnd } f := float64(lvl - fStart) f *= float64(tEnd-tStart) / float64(fEnd-fStart) res = int32(math.Round(f)) return tStart + res }
package stdlog import ( "io" "log" "os" "strings" "git.fractalqb.de/fractalqb/c4hgol" ) type dontWrite struct{} func (dw dontWrite) Write(p []byte) (n int, err error) { return 0, nil } type Config struct { log *log.Logger origWr io.Writer enabled bool } func (c *Config) Name() string { res := strings.TrimSpace(log.Prefix()) return res } func (c *Config) Enabled() bool { return c.enabled } func (c *Config) Enable(setEnabled bool) { if setEnabled { if !c.enabled { c.log.SetOutput(c.origWr) c.enabled = true } } else if c.enabled { c.log.SetOutput(dontWrite{}) c.enabled = false } } func (c *Config) Output() io.Writer { return c.origWr } func (c *Config) SetOutput(wr io.Writer) { c.log.SetOutput(wr) c.origWr = wr } func NewConfig(logger *log.Logger, original io.Writer, reuse *Config) *Config { if reuse == nil { reuse = new(Config) } *reuse = Config{log: logger, enabled: true} if original == nil { reuse.origWr = os.Stderr } else { reuse.origWr = original } return reuse } func (c *Config) Level() c4hgol.Level { return c4hgol.Info } func (c *Config) SetLevel(l c4hgol.Level) {} var _ c4hgol.Configurer = (*Config)(nil)
package stdlog import ( "io" "log" "os" "strings" "git.fractalqb.de/fractalqb/c4hgol" ) type stdCfg struct { origWr io.Writer enabled bool } var StdCfg c4hgol.Configurer = &stdCfg{ origWr: os.Stderr, enabled: true, } func (c *stdCfg) Name() string { res := strings.TrimSpace(log.Prefix()) return res } func (c *stdCfg) Enabled() bool { return c.enabled } func (c *stdCfg) Enable(setEnabled bool) { if setEnabled { if !c.enabled { log.SetOutput(c.origWr) c.enabled = true } } else if c.enabled { log.SetOutput(dontWrite{}) c.enabled = false } } func (c *stdCfg) Level() c4hgol.Level { return c4hgol.Info } func (c *stdCfg) SetLevel(l c4hgol.Level) {} func (c *stdCfg) Output() io.Writer { return c.origWr } func (c *stdCfg) SetOutput(wr io.Writer) { log.SetOutput(wr) c.origWr = wr }