├── .gitignore ├── go.mod ├── whatisnop.go ├── go.sum ├── whatpackagenop.go ├── whatfuncnop.go ├── whathappensnop.go ├── whatfunc.go ├── whatpackage.go ├── funcinfo.go ├── whatis.go ├── doc.go ├── whathappens.go ├── LICENSE.txt ├── enable_test.go ├── enable.go ├── colorize.go ├── what_test.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.code-workspace 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module appliedgo.net/what 2 | 3 | go 1.18 4 | 5 | require github.com/davecgh/go-spew v1.1.1 6 | -------------------------------------------------------------------------------- /whatisnop.go: -------------------------------------------------------------------------------- 1 | //go:build !what && !whatis 2 | 3 | package what 4 | 5 | // Is pretty-prints data. 6 | func Is(v any) { 7 | } 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | -------------------------------------------------------------------------------- /whatpackagenop.go: -------------------------------------------------------------------------------- 1 | //go:build !what && !whatpackage 2 | 3 | package what 4 | 5 | // Package prints the code's package name, including the 6 | // parent path entry if any. 7 | func Package() {} 8 | -------------------------------------------------------------------------------- /whatfuncnop.go: -------------------------------------------------------------------------------- 1 | //go:build !what && !whatfunc 2 | 3 | package what 4 | 5 | // Func logs the current function name, line number, and file name. 6 | // Useful for tracing function calls 7 | func Func() {} 8 | -------------------------------------------------------------------------------- /whathappensnop.go: -------------------------------------------------------------------------------- 1 | //go:build !what && !whathappens 2 | 3 | package what 4 | 5 | // Happens logs the current function name and whatever message is passed in. 6 | func Happens(fmt string, args ...any) {} 7 | 8 | // If works like Happens but only if yes is true. 9 | func If(yes bool, fmt string, args ...any) {} 10 | -------------------------------------------------------------------------------- /whatfunc.go: -------------------------------------------------------------------------------- 1 | //go:build what || whatfunc 2 | 3 | package what 4 | 5 | import ( 6 | "log" 7 | ) 8 | 9 | // Func logs the current function name, line number, and file name. 10 | // Useful for tracing function calls 11 | func Func() { 12 | name, file, line := funcinfo(2) 13 | log.Printf("Func %s in line %d of file %s\n", name, line, file) 14 | } 15 | -------------------------------------------------------------------------------- /whatpackage.go: -------------------------------------------------------------------------------- 1 | //go:build what || whatpackage 2 | 3 | package what 4 | 5 | import ( 6 | "log" 7 | ) 8 | 9 | // Package prints the code's package name, including the 10 | // parent path entry if any. 11 | func Package() { 12 | pkg, prnt := pkgname(2) 13 | if len(prnt) > 0 { 14 | log.Printf("Package %s/%s\n", prnt, pkg) 15 | } else { 16 | log.Printf("Package %s\n", pkg) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /funcinfo.go: -------------------------------------------------------------------------------- 1 | //go:build what || whathappens || whatfunc 2 | 3 | package what 4 | 5 | import "runtime" 6 | 7 | // funcinfo returns func name, line, and file name of a 8 | // caller, after skipping skip callers. 9 | func funcinfo(skip int) (name, file string, line int) { 10 | pc, file, line, ok := runtime.Caller(skip) 11 | if ok { 12 | name = runtime.FuncForPC(pc).Name() 13 | } 14 | return name, file, line 15 | } 16 | 17 | // funcname returns the name of a 18 | // caller, after skipping skip callers. 19 | func funcname(skip int) string { 20 | name, _, _ := funcinfo(skip + 1) 21 | return name 22 | } 23 | -------------------------------------------------------------------------------- /whatis.go: -------------------------------------------------------------------------------- 1 | //go:build what || whatis 2 | 3 | package what 4 | 5 | import ( 6 | "log" 7 | 8 | "github.com/davecgh/go-spew/spew" 9 | ) 10 | 11 | // DebugString() is a pseudo-standard for printing debug info. 12 | // HT to DoltHub: https://www.dolthub.com/blog/2025-01-03-gos-debug-string-pseudo-standard/ 13 | type DebugStringer interface { 14 | DebugString() string 15 | } 16 | 17 | // Is pretty-prints data. 18 | func Is(v any) { 19 | // if v is a DebugStringer, have it print its debug info. 20 | if ds, ok := v.(DebugStringer); ok { 21 | spew.Fprint(log.Writer(), ds.DebugString()) 22 | return 23 | } 24 | // any non-DebugStringer -> dump the data. 25 | spew.Fdump(log.Writer(), v) 26 | 27 | } 28 | 29 | func init() { 30 | // Default indent of one space is too narrow for my taste 31 | spew.Config.Indent = " " 32 | } 33 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package what provides a quick and convenient way of adding development-only log statements to your code. 3 | 4 | Instead of firing up the debugger and stepping through each line one-by-one, spread a few what calls across the code you want to inspect, then run the code and watch the output. 5 | 6 | Log output from what must be enabled through build tags. 7 | This ensures that your debug logging does not leak into production code and involuntarily exposes sensitive data to prying eyes. Use the log package for generating user-facing log output from production code. 8 | 9 | Enable what by passing "what" as a build tag: 10 | 11 | go build -tags what 12 | 13 | Enable only parts of what by passing the respective build tag: whathappens, whatis, whatfunc, or whatpackage. (Good for reducing noise, e.g by muting what.Func().) 14 | 15 | Functions that are not enabled by a build tag become no-ops. 16 | 17 | Enable what for particular packages only by setting the environment variable WHAT to a package name or a comma-separated list of package names: 18 | 19 | export WHAT=pkg1,pkg2 20 | 21 | (Also good for reducing noise.) 22 | */ 23 | package what 24 | -------------------------------------------------------------------------------- /whathappens.go: -------------------------------------------------------------------------------- 1 | //go:build what || whathappens 2 | 3 | package what 4 | 5 | import ( 6 | "log" 7 | ) 8 | 9 | // Happens logs the current function name and whatever message is passed in. The fmt string can use any formatting verbs as defined by the `fmt` package. 10 | // Exception: If the fmt string is either of DEBUG, INFO, WARN, or ERROR, Happens switches to structured logging with colorization. In this case, the args must follow `log/slog` convention, that is: "message", "key1", value1, "key2", value2, etc. 11 | // Happens prefixes the output with the current time, the log level, and the function name. 12 | func Happens(fmt string, args ...any) { 13 | if isPackageEnabled() { 14 | // First, attempt to read a structured log message and colorize the output. 15 | switch fmt { 16 | case "DEBUG", "INFO", "WARN", "ERROR": 17 | log.SetFlags(0) 18 | log.Print(colorize(fmt, args...)) 19 | default: 20 | log.Printf(funcname(2)+": "+fmt+"\n", args...) 21 | } 22 | } 23 | } 24 | 25 | // If works like Happens but only if yes is true. 26 | func If(yes bool, fmt string, args ...any) { 27 | if yes && isPackageEnabled() { 28 | log.Printf(funcname(2)+": "+fmt+"\n", args...) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Christoph Berger 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /enable_test.go: -------------------------------------------------------------------------------- 1 | //go:build what || whathappens || whatif || whatfunc 2 | 3 | package what 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func Test_pkgname(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | want string 16 | }{ 17 | { 18 | "what", 19 | "appliedgo.net/what", 20 | }, 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | got := "" 25 | pkg, prnt := pkgname(1) 26 | if len(pkg) > 0 { 27 | got = fmt.Sprintf("%s/%s", prnt, pkg) 28 | } else { 29 | got = pkg 30 | } 31 | if got != tt.want { 32 | t.Errorf("pkgname() = %v, want %v", got, tt.want) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | func Test_getenvWhat(t *testing.T) { 39 | tests := []struct { 40 | name string 41 | packages string 42 | want map[string]bool 43 | }{ 44 | { 45 | "Single package", 46 | "pkg1", 47 | map[string]bool{ 48 | "pkg1": true, 49 | }, 50 | }, 51 | { 52 | "Two packages", 53 | "pkg1,pkg2", 54 | map[string]bool{ 55 | "pkg1": true, 56 | "pkg2": true, 57 | }, 58 | }, 59 | { 60 | "Two qualified paths", 61 | "path/to/my/pkg1,repohost.com/user/pkg2", 62 | map[string]bool{ 63 | "path/to/my/pkg1": true, 64 | "repohost.com/user/pkg2": true, 65 | }, 66 | }, 67 | } 68 | if len(os.Getenv("WHAT")) > 0 { 69 | t.Skip("Someone is using WHAT already, skipping") 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | os.Setenv("WHAT", tt.packages) 74 | getenvWhat() 75 | if !reflect.DeepEqual(enabled, tt.want) { 76 | t.Errorf("Error reading WHAT. Got: %v, want: %v", enabled, tt.want) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /enable.go: -------------------------------------------------------------------------------- 1 | //go:build what || whathappens || whatif || whatfunc || whatis || whatpackage 2 | 3 | package what 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | var enabled map[string]bool 12 | 13 | func isPackageEnabled() bool { 14 | if len(enabled) == 0 { // all packages enabled 15 | return true 16 | } 17 | pkg, prnt := pkgname(3) // 3 = skip isPackageEnabled and the top-level what.X function 18 | if enabled[pkg] { 19 | return true 20 | } 21 | if enabled[prnt+"/"+pkg] { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | // pkgname returns the package name and the direct parent in the package path 28 | // of the skip'th caller 29 | func pkgname(skip int) (string, string) { 30 | pc, _, _, _ := runtime.Caller(skip) 31 | fn := runtime.FuncForPC(pc).Name() 32 | // possible fn formats: 33 | // * /some/path/to/package.(Receiver).Func 34 | // * /some/path/to/package.Func.func1 (closure) 35 | // * /some/path/to/package.Func 36 | // * pathto/package.Func 37 | // * package.Func (for package main, and if someone hacks 38 | // the stdlib and adds `what` calls there.) 39 | 40 | startName, startParent, endName := 0, 0, 0 41 | 42 | // Search the last /, it is the beginning of the package name 43 | for i := len(fn) - 1; i >= 0; i-- { 44 | if fn[i] == '/' { 45 | startName = i + 1 46 | break 47 | } 48 | } 49 | // post-loop assert: startName is either 0 (for package main) or i+1 50 | 51 | // Search the first dot after the last /, it is the end of the package name 52 | for i := startName; i < len(fn); i++ { 53 | if fn[i] == '.' { 54 | endName = i 55 | break 56 | } 57 | } 58 | 59 | // no leading / found means we are probably in package main. 60 | if startName == 0 { 61 | return fn[0:endName], "" 62 | } 63 | 64 | // Search the second-last /, it is the beginning of the parent's name 65 | endParent := startName - 1 66 | for i := endParent - 1; i >= 0; i-- { 67 | if fn[i] == '/' { 68 | startParent = i + 1 69 | break 70 | } 71 | } 72 | // startParent is 0 in case of pathto/package.Func 73 | 74 | return fn[startName:endName], fn[startParent:endParent] 75 | } 76 | 77 | func getenvWhat() { 78 | packages := strings.Split(os.Getenv("WHAT"), ",") 79 | enabled = map[string]bool{} 80 | for _, p := range packages { 81 | if p != "" { 82 | enabled[p] = true 83 | } 84 | } 85 | } 86 | 87 | func init() { 88 | getenvWhat() 89 | } 90 | -------------------------------------------------------------------------------- /colorize.go: -------------------------------------------------------------------------------- 1 | //go:build what || whathappens || whatif || whatfunc || whatis || whatpackage 2 | 3 | // Based on https://github.com/spotlightpa/almanack/blob/master/pkg/almlog/colorize.go by Carlana Johnson. 4 | 5 | package what 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | const ( 14 | reset = "\033[0m" 15 | bold = "\033[1m" 16 | dim = "\033[2m" 17 | standout = "\033[3m" 18 | underscore = "\033[4m" 19 | blink = "\033[5m" 20 | blinkmore = "\033[6m" 21 | invert = "\033[7m" 22 | hide = "\033[8m" 23 | del = "\033[9m" 24 | black = "\033[30m" 25 | red = "\033[31m" 26 | green = "\033[32m" 27 | yellow = "\033[33m" 28 | blue = "\033[34m" 29 | magenta = "\033[35m" 30 | cyan = "\033[36m" 31 | white = "\033[37m" 32 | purple = magenta + bold 33 | ) 34 | 35 | func colorize(level string, args ...any) string { 36 | // Build the prefix: time, level, func, msg... 37 | logArgs := []any{"time", time.Now().UTC().Format("2006-01-02 15:04:05"), "level", level, "func", funcname(3), "msg", args[0]} 38 | 39 | // ...then append the key/value pairs 40 | logArgs = append(logArgs, args[1:]...) 41 | 42 | var buf bytes.Buffer 43 | 44 | // Iterate over the logArgs and apply formatting 45 | for i := 0; i < len(logArgs); i += 2 { 46 | key := fmt.Sprint(logArgs[i]) 47 | val := fmt.Sprint(logArgs[i+1]) 48 | 49 | keyColor := cyan 50 | valColor := magenta 51 | withKey := true 52 | 53 | switch key { 54 | case "time": 55 | withKey = false 56 | valColor = dim 57 | case "level": 58 | withKey = false 59 | switch level { 60 | case "DEBUG": 61 | valColor = dim 62 | case "INFO": 63 | valColor = green 64 | case "WARN": 65 | valColor = yellow 66 | case "ERROR": 67 | valColor = red + bold 68 | } 69 | case "func": 70 | withKey = false 71 | valColor = blue 72 | case "msg": 73 | withKey = false 74 | valColor = white + underscore 75 | case "err": 76 | valColor = red + bold 77 | } 78 | 79 | // Coloring for value based on level 80 | 81 | if withKey { 82 | // Format as `key=value` 83 | buf.WriteString(keyColor) 84 | buf.WriteString(key) 85 | buf.WriteString(reset) 86 | buf.WriteString("=") 87 | } 88 | buf.WriteString(valColor) 89 | buf.WriteString(val) 90 | buf.WriteString(reset) 91 | buf.WriteString(" ") 92 | } 93 | 94 | return buf.String() 95 | } 96 | -------------------------------------------------------------------------------- /what_test.go: -------------------------------------------------------------------------------- 1 | //go:build what || whatpackage || whatis || whatfunc || whathappens || whatif 2 | 3 | package what 4 | 5 | // IMPORTANT: to run these tests, use build tag "what": 6 | // 7 | // go test -tags what 8 | 9 | import ( 10 | "bytes" 11 | "io" 12 | "log" 13 | "os" 14 | "regexp" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | func TestOutput(t *testing.T) { 20 | got := &bytes.Buffer{} 21 | log.SetOutput(NewTeeWriter(got, os.Stderr)) // write all log output into "got" for later matching 22 | log.SetFlags(0) // no extra decorations 23 | n := 23 24 | 25 | // no package name set - all packages are enabled 26 | enabled = map[string]bool{} 27 | Happens("what.Happens") 28 | verify(t, got, `appliedgo\.net/what\.TestOutput: what\.Happens`) 29 | got.Reset() 30 | // Structured output should get colorized: 31 | Happens("WARN", "Something is fishy", "code", 23, "err", "Quartotube exceeded max flotic burst") 32 | verify(t, got, strings.Replace(dim+`\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d`+reset+` `+yellow+`WARN`+reset+" "+blue+`appliedgo.net/what.TestOutput`+reset+` `+white+underscore+`Something is fishy`+reset+" "+cyan+`code`+reset+`=`+magenta+`23`+reset+` `+cyan+`err`+reset+`=`+red+bold+`Quartotube exceeded max flotic burst`+reset+" ", `[`, `\[`, -1)) 33 | got.Reset() 34 | If(true, "If true") 35 | verify(t, got, `appliedgo\.net/what\.TestOutput: If true`) 36 | got.Reset() 37 | If(false, "If false") 38 | verify(t, got, ``) 39 | got.Reset() 40 | Is(n) 41 | verify(t, got, `\(int\) 23`) 42 | got.Reset() 43 | Func() 44 | verify(t, got, `Func appliedgo\.net/what\.TestOutput in line \d+ of file .*/what_test\.go`) 45 | got.Reset() 46 | Package() 47 | verify(t, got, `Package appliedgo\.net/what`) 48 | } 49 | 50 | func verify(t *testing.T, gotbuf *bytes.Buffer, want string) { 51 | wantRE := regexp.MustCompile("^" + want + "$") 52 | got := gotbuf.String()[:max(0, gotbuf.Len()-1)] // trim the trailing \n 53 | if !wantRE.MatchString(got) { 54 | t.Errorf("Got: `%s` Want: `%s`", got, want) 55 | } 56 | } 57 | 58 | type testStruct struct { 59 | name string 60 | } 61 | 62 | // Implement DebugStringer for testStruct 63 | func (ts testStruct) DebugString() string { 64 | return "DEBUG:" + ts.name 65 | } 66 | 67 | // TestDebugStringer tests the support for DebugString() 68 | func TestDebugStringer(t *testing.T) { 69 | got := &bytes.Buffer{} 70 | log.SetOutput(got) 71 | log.SetFlags(0) 72 | 73 | test := testStruct{name: "Test"} 74 | Is(test) 75 | 76 | want := "DEBUG:Test" 77 | if got.String() != want { 78 | t.Errorf("Got: `%s` Want: `%s`", got.String(), want) 79 | } 80 | } 81 | 82 | func TestEnabling(t *testing.T) { 83 | got := &bytes.Buffer{} 84 | log.SetOutput(got) // write all log output into "got" for later matching 85 | log.SetFlags(0) // no extra decorations 86 | 87 | // no package name set - all packages are enabled 88 | enabled = map[string]bool{} 89 | Happens("what.Happens - all packages enabled") 90 | 91 | enabled = map[string]bool{ 92 | "what": true, 93 | } 94 | Happens("what.Happens - package 'what' enabled") 95 | 96 | enabled = map[string]bool{ 97 | "appliedgo.net/what": true, 98 | } 99 | Happens("what.Happens - package 'appliedgo.net/what' enabled") 100 | 101 | enabled = map[string]bool{ 102 | "someotherpackage": true, 103 | } 104 | Happens("what.Happens - package 'what' NOT enabled") // this should not print 105 | 106 | wantRE := regexp.MustCompile(`appliedgo.net/what\.TestEnabling: what\.Happens - all packages enabled 107 | appliedgo\.net/what\.TestEnabling: what.Happens - package 'what' enabled 108 | appliedgo\.net/what\.TestEnabling: what.Happens - package 'appliedgo\.net/what' enabled 109 | `) 110 | // "got" contains all log output from the above calls 111 | if !wantRE.Match(got.Bytes()) { 112 | t.Errorf("Got: %s\n\nWant: %s", got, wantRE) 113 | } 114 | } 115 | 116 | // for pre-Go 1.21 clients 117 | func max(a, b int) int { 118 | if a > b { 119 | return a 120 | } 121 | return b 122 | } 123 | 124 | type TeeWriter struct { 125 | writers []io.Writer 126 | } 127 | 128 | // NewTeeWriter creates a new TeeWriter with the given io.Writers. 129 | func NewTeeWriter(writers ...io.Writer) *TeeWriter { 130 | return &TeeWriter{writers: writers} 131 | } 132 | 133 | // Write writes bytes to each of the writers in TeeWriter, returning the number 134 | // of bytes written and an error, if any occurs. 135 | func (t *TeeWriter) Write(p []byte) (n int, err error) { 136 | for _, w := range t.writers { 137 | n, err = w.Write(p) 138 | if err != nil { 139 | return n, err 140 | } 141 | } 142 | return len(p), nil 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What: debug-level logging that vanishes from production code 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/appliedgo.net/what.svg)](https://pkg.go.dev/appliedgo.net/what) 4 | 5 | ## How to import the package 6 | 7 | ```go 8 | import "appliedgo.net/what" 9 | ``` 10 | (Do not use the direct path to the repo.) 11 | 12 | ## What does what do 13 | 14 | `what` is a set of simple and easy logging functions, suitable for tracing any kind of activities in your code. `what` can print the current function name, quickly `Printf`-format your data, and dumps data structures. 15 | 16 | And last not least, no `what` calls reach your production binary (unless you want it so). Debug-level logging is for developers only. *No more accidental data leaks in production through left-over debug logging statements.* 17 | 18 | 19 | ## Who need this? 20 | 21 | You definitely should give `what` a closer look if you - 22 | 23 | * heartily agree to [Dave Cheney's article about logging](https://dave.cheney.net/2015/11/05/lets-talk-about-logging), or 24 | * want to keep your production code free from *any* log or trace output. (Think security!) 25 | 26 | ## How does it work? 27 | 28 | First of all, `what` is intended for debug-level logging *only*. So, 29 | 30 | * Use `what` for tracing and debugging your code. ("Does my code do what I intended? Does this variable contain what I expect? Why does the loop not stop when the break condition *should* be fulfilled?...") 31 | * Use `log` for user-facing log output. ("What was the app doing before it said, 'cannot connect to server'? Did that service already sync or is it still waiting for other services?...") 32 | 33 | You have to explicitly enable `what` logging through build flags (see below). 34 | 35 | ## Why not just firing up a debugger? 36 | 37 | `what` is one of many debugging techniques. Sometimes, a little log output can prevent a time-consuming debugger session. `what` does not replace but complement your debugger. 38 | 39 | ### Available functions 40 | 41 | ```go 42 | what.Happens("Foo: %s", bar) // log.Printf("Foo: %s\n", bar) 43 | what.Happens("INFO", "message", "key1", value1) // like slog.Info() 44 | what.If(cond, "Foo: %s", bar) // only print if cond is true 45 | what.Func() // Print out the fully qualified function name 46 | what.Is(var) // Dump the structure and contents of var. Is() recognizes a DebugStringer. 47 | what.Package() // Print the current package's name 48 | ``` 49 | 50 | Spread these calls across your code, especially in places you want to observe closer. 51 | 52 | `what.Happens()` has two modes. 53 | 54 | If the format string is either of "DEBUG", "INFO", "WARN", or "ERROR", the behavior is like `slog.Debug()`, `slog.Info()`, and so on. The first argument after the level keyword is the log message, and all subsequent arguments are key/value pairs, where the value can be of any type. 55 | 56 | If the format string is none of the above keywords, `what.Happens()` behaves like `log.Printf()`. 57 | 58 | Debug-level logging with `what` is useful alongside unit testing as well as using a debugger. It does not attempt to replace any of these concepts. 59 | 60 | ### Enabling and disabling 61 | 62 | `what` logging can be enabled and disabled through build tags. 63 | 64 | #### Enable all functions 65 | 66 | Simply pass the `what` tag to `go build`, `go install`, `go test` etc: 67 | 68 | ```sh 69 | go build -tags what 70 | ``` 71 | 72 | And now just lean back and see your code talking about what it does. 73 | 74 | #### Enable specific functions 75 | 76 | To reduce the noise, you can decide to compile only specific parts of `what`: 77 | 78 | * `whathappens` only enables `what.Happens()` and `what.If()`. 79 | * `whatis` only enables `what.Is()`. 80 | * `whatfunc` only enables `what.Func()`. 81 | * `whatpackage` only enables `what.Package()`. 82 | 83 | All disabled functions get replaced by no-ops. 84 | 85 | Example: 86 | 87 | ```sh 88 | go build -tags whathappens 89 | ``` 90 | 91 | You can also choose a combination of the above, for example: `go build -tags whathappens,whatis` 92 | 93 | 94 | #### Enable debug logging for specific packages only 95 | 96 | Go's build tag mechanism cannot help here, so this is done through an environment variable called "WHAT". 97 | 98 | To enable specific packages for debug logging, set `WHAT` to a package name, or a list of package names. 99 | 100 | 101 | 102 | #### Disable what 103 | 104 | Nothing easier than that! Without any of the above build tags, all functions get replaced by no-ops, ready for being optimized away entirely (if the compiler decides to do so). 105 | 106 | * No log output 107 | * No bloated binary 108 | * No security leak from chatty binaries. 109 | 110 | ## Non-features 111 | 112 | * Uses only stdlib `log`, no custom logger configurable. 113 | * No custom variable dumper/pretty-printer. At the moment, `what` uses `github.com/davecgh/go-spew`. See Spew's docs about the syntax used for printing a variable. 114 | 115 | ## Restrictions 116 | 117 | Although `go run` should recognize all build flags that `go build` recognizes (including `-tags`), it seems that `go run main.go -tags what` does not consider the `what` tag. Use `go build -tags what && ./main` instead. 118 | 119 | ## Compatibility 120 | 121 | - v0.1.6 and later require Go 1.18 (replacement of `interface{}` with `any`) 122 | --------------------------------------------------------------------------------