From 98a14b1bf8ab4b45f62eee98236a72109acbc316 Mon Sep 17 00:00:00 2001 From: grumbulon Date: Wed, 29 Jun 2022 22:57:03 +0000 Subject: [PATCH] Merge logawl package into master (#10) This PR is to bring in the new logging package for awl, I wanted to do some refinements before I opened this to upstream logawl works in the following ways 1. It prints logs directly to std.err 1. The default log level is Info (this can be changed multiple ways) 1. It supports four log levels Error, Fatal, Info, Debug 1. It differs from the syslog package in the stdlib wherin it default to std.err you do not _need_ to define an out file (syslog for example) 1. I added a "debug" flag so now we can go through and define verbose logging through the app 5.5 I made it so we can call `Logger.debug("message")` anywhere in the query file but unless the debug flag is set to true it will not print a message (it is working as intended finally). Co-authored-by: grumbulon Reviewed-on: https://git.froth.zone/sam/awl/pulls/10 Co-authored-by: grumbulon Co-committed-by: grumbulon --- cli.go | 5 ++ logawl/doc.go | 30 +++++++++++ logawl/logawl.go | 72 ++++++++++++++++++++++++++ logawl/logger.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++ query.go | 12 +++-- 5 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 logawl/doc.go create mode 100644 logawl/logawl.go create mode 100644 logawl/logger.go diff --git a/cli.go b/cli.go index f59b880..5fee552 100644 --- a/cli.go +++ b/cli.go @@ -133,6 +133,11 @@ func prepareCLI() *cli.App { Aliases: []string{"x"}, Usage: "do a reverse lookup", }, + &cli.BoolFlag{ + Name: "debug", + Usage: "enable debug logging", + Value: false, + }, }, Action: doQuery, } diff --git a/logawl/doc.go b/logawl/doc.go new file mode 100644 index 0000000..b67c5ea --- /dev/null +++ b/logawl/doc.go @@ -0,0 +1,30 @@ +/* +LogAwl is a package for custom logging needs + +LogAwl extends the standard log library with support for log levels +This is _different_ from the syslog package in the standard library because you do not define a file +because awl is a cli utility it writes directly to std err. +*/ +// Use the New() function to init logawl +// +// logger := logawl.New() +// +// You can call specific logging levels from your new logger using +// +// logger.Debug("Message to log") +// logger.Fatal("Message to log") +// logger.Info("Message to log") +// logger.Error("Message to log") +// +// You may also set the log level on the fly with +// +// Logger.SetLevel(3) +// This allows you to change the default level (Info) and prevent log messages from being posted at higher verbosity levels +//for example if +// Logger.SetLevel(3) +// is not called and you call +// Logger.Debug() +// this runs through +// IsLevel(level) +// to verify if the debug log should be sent to std.Err or not based on the current expected log level +package logawl diff --git a/logawl/logawl.go b/logawl/logawl.go new file mode 100644 index 0000000..e0beafd --- /dev/null +++ b/logawl/logawl.go @@ -0,0 +1,72 @@ +package logawl + +import ( + "fmt" + "io" + "sync" + "sync/atomic" +) + +type Level int32 +type Logger struct { + Mu sync.Mutex + Level Level + Prefix string + Out io.Writer + buf []byte + isDiscard int32 +} + +// Stores whatever input value is in mem address of l.level +func (l *Logger) SetLevel(level Level) { + atomic.StoreInt32((*int32)(&l.Level), int32(level)) +} + +// Mostly nothing +func (l *Logger) GetLevel() Level { + return l.level() +} + +// Retrieves whatever was stored in mem address of l.level +func (l *Logger) level() Level { + return Level(atomic.LoadInt32((*int32)(&l.Level))) +} + +// Unmarshalls the int value of level for writing the header +func (l *Logger) UnMarshalLevel(lv Level) (string, error) { + switch lv { + case 0: + return "FATAL ", nil + case 1: + return "ERROR ", nil + case 2: + return "INFO ", nil + case 3: + return "DEBUG ", nil + } + return "", fmt.Errorf("Invalid log level choice") +} + +func (l *Logger) IsLevel(level Level) bool { + return l.level() >= level +} + +var AllLevels = []Level{ + FatalLevel, + ErrorLevel, + InfoLevel, + DebugLevel, +} + +const ( + // Fatal logs (will call exit(1)) + FatalLevel Level = iota + + // Error logs + ErrorLevel + + // What is going on level + InfoLevel + // Verbose log level. + DebugLevel +) diff --git a/logawl/logger.go b/logawl/logger.go new file mode 100644 index 0000000..b101b7e --- /dev/null +++ b/logawl/logger.go @@ -0,0 +1,130 @@ +package logawl + +import ( + "fmt" + "os" + "sync/atomic" + "time" +) + +// Calling New instantiates Logger +// +// Level can be changed to one of the other log levels (FatalLevel, ErrorLevel, InfoLevel, DebugLevel) +func New() *Logger { + return &Logger{ + Out: os.Stderr, + Level: InfoLevel, //Default value is InfoLevel + } +} + +// Takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)) +func (l *Logger) Println(level Level, v ...any) { + if atomic.LoadInt32(&l.isDiscard) != 0 { + return + } + //If verbose is not set --debug etc print _nothing_ + if l.IsLevel(level) { + switch level { //Goes through log levels and does stuff based on them (Fatal os.Exit...etc) + case 0: + l.Printer(0, fmt.Sprintln(v...)) //Fatal level + os.Exit(1) + case 1: + l.Printer(1, fmt.Sprintln(v...)) //Error level + os.Exit(2) + case 2: + l.Printer(2, fmt.Sprintln(v...)) //Info level + case 3: + l.Printer(3, fmt.Sprintln(v...)) //Debug level + default: + break + } + } +} + +// Formats the log header as such YYYY/MM/DD HH:MM:SS (local time) +func (l *Logger) formatHeader(buf *[]byte, t time.Time, line int, level Level) { + if lvl, err := l.UnMarshalLevel(level); err == nil { + // This is ugly but functional + // maybe there can be an append func or something in the future + *buf = append(*buf, lvl...) + year, month, day := t.Date() + *buf = append(*buf, '[') + formatter(buf, year, 4) + *buf = append(*buf, '/') + formatter(buf, int(month), 2) + *buf = append(*buf, '/') + formatter(buf, day, 2) + *buf = append(*buf, ' ') + hour, min, sec := t.Clock() + formatter(buf, hour, 2) + *buf = append(*buf, ':') + formatter(buf, min, 2) + *buf = append(*buf, ':') + formatter(buf, sec, 2) + *buf = append(*buf, ']') + *buf = append(*buf, ':') + *buf = append(*buf, ' ') + } else { + fmt.Printf("Unable to unmarshal log level: %v", err) + os.Exit(2) //Fucking kill him + } + +} + +// Printer prints the formatted message directly to stdErr +func (l *Logger) Printer(level Level, s string) error { + now := time.Now() + var line int + l.Mu.Lock() + defer l.Mu.Unlock() + + l.buf = l.buf[:0] + l.formatHeader(&l.buf, now, line, level) + l.buf = append(l.buf, s...) + if len(s) == 0 || s[len(s)-1] != '\n' { + l.buf = append(l.buf, '\n') + } + _, err := l.Out.Write(l.buf) + return err +} + +// Some line formatting stuff from Golang log stdlib file +// +// Please view https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/log/log.go;drc=41e1d9075e428c2fc32d966b3752a3029b620e2c;l=96 +// +// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. +func formatter(buf *[]byte, i int, wid int) { + // Assemble decimal in reverse order. + var b [20]byte + bp := len(b) - 1 + for i >= 10 || wid > 1 { + wid-- + q := i / 10 + b[bp] = byte('0' + i - q*10) + bp-- + i = q + } + // i < 10 + b[bp] = byte('0' + i) + *buf = append(*buf, b[bp:]...) +} + +// Call print directly with Debug level +func (l *Logger) Debug(v ...any) { + l.Println(DebugLevel, v...) +} + +// Call print directly with Info level +func (l *Logger) Info(v ...any) { + l.Println(InfoLevel, v...) +} + +// Call print directly with Error level +func (l *Logger) Error(v ...any) { + l.Println(ErrorLevel, v...) +} + +// Call print directly with Fatal level +func (l *Logger) Fatal(v ...any) { + l.Println(FatalLevel, v...) +} diff --git a/query.go b/query.go index 57753b5..a84ff6c 100644 --- a/query.go +++ b/query.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "git.froth.zone/sam/awl/logawl" "git.froth.zone/sam/awl/query" "git.froth.zone/sam/awl/util" "github.com/miekg/dns" @@ -19,15 +20,20 @@ func doQuery(c *cli.Context) error { err error resp util.Response isHTTPS bool + Logger = logawl.New() //init logger ) resp.Answers, err = parseArgs(c.Args().Slice()) if err != nil { + Logger.Error("Unable to parse args") return err } - port := c.Int("port") + if c.Bool("debug") { + Logger.SetLevel(3) + } + Logger.Debug("Starting awl") // If port is not set, set it if port == 0 { if c.Bool("tls") || c.Bool("quic") { @@ -67,8 +73,6 @@ func doQuery(c *cli.Context) error { msg.SetQuestion(resp.Answers.Name, resp.Answers.Request) - // TODO: maybe not make this a gross chunk of if statements? who knows - // Make this authoritative (does this do anything?) if c.Bool("aa") { msg.Authoritative = true @@ -79,6 +83,7 @@ func doQuery(c *cli.Context) error { } // Set the zero flag if requested (does nothing) if c.Bool("z") { + Logger.Debug("Setting message to zero") msg.Zero = true } // Disable DNSSEC validation @@ -95,6 +100,7 @@ func doQuery(c *cli.Context) error { } // Set DNSSEC if requested if c.Bool("dnssec") { + Logger.Debug("Using DNSSEC") msg.SetEdns0(1232, true) }