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) }