diff --git a/README.md b/README.md index ee586bb..e48c628 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ These documents are based on the `Gray/Black Hat Python/C#` series of books. I l + [02.6 - Goroutines and channels](content/02.6.md) + [02.7 - Error handling](content/02.7.md) - [03 - Useful Go packages - WIP](content/03.0.md) - + [03.1 - flag](content/03.1.md) + + [03.1 - flag package](content/03.1.md) + + [03.2 - log package](content/03.2.md) - [04- Go networking](content/04.0.md) + [04.1 - Basic TCP and UDP clients](content/04.1.md) + [04.2 - TCP servers](content/04.2.md) diff --git a/code/03/03.2/03.2-01-basic-logging.go b/code/03/03.2/03.2-01-basic-logging.go new file mode 100755 index 0000000..f967728 --- /dev/null +++ b/code/03/03.2/03.2-01-basic-logging.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" +) + +func main() { + + a, b := 10, 20 + + log.Print("Use Print to log.") + log.Println("Ditto for Println.") + log.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} diff --git a/code/03/03.2/03.2-02-log-file.go b/code/03/03.2/03.2-02-log-file.go new file mode 100755 index 0000000..18d3f3d --- /dev/null +++ b/code/03/03.2/03.2-02-log-file.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "os" +) + +func main() { + + // Create a file + logFile, err := os.Create("log1.txt") + if err != nil { + panic("Could not open file") + } + + // Close the file after main returns + defer logFile.Close() + + a, b := 10, 20 + + // We will not use the other options + myLog := log.New(logFile, "", 0) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} diff --git a/code/03/03.2/03.2-03-log-multiple-files.go b/code/03/03.2/03.2-03-log-multiple-files.go new file mode 100755 index 0000000..6b4cda0 --- /dev/null +++ b/code/03/03.2/03.2-03-log-multiple-files.go @@ -0,0 +1,47 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "os" +) + +func main() { + + // Create a file + logFile, err := os.Create("log1.txt") + if err != nil { + panic("Could not open file") + } + + // Close the file after main returns + defer logFile.Close() + + // Create a second file + logFile2, err := os.Create("log2.txt") + if err != nil { + panic("Could not open file2") + } + + defer logFile2.Close() + + // Create a buffer + var buflog bytes.Buffer + + multiW := io.MultiWriter(logFile, logFile2, &buflog, os.Stdout) + + a, b := 10, 20 + + // Log to multiW + myLog := log.New(multiW, "", 0) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) + + // Print buffer + fmt.Println("Buffer:") + fmt.Println(buflog.String()) +} diff --git a/code/03/03.2/03.2-04-log-flags.go b/code/03/03.2/03.2-04-log-flags.go new file mode 100755 index 0000000..6734aeb --- /dev/null +++ b/code/03/03.2/03.2-04-log-flags.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os" +) + +func main() { + + a, b := 10, 20 + + // New logger will output to stdout with flags + // Only log date and file + myLog := log.New(os.Stdout, "", log.Ldate|log.Lshortfile) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} diff --git a/code/03/03.2/03.2-05-log-prefix.go b/code/03/03.2/03.2-05-log-prefix.go new file mode 100755 index 0000000..7ab5d8e --- /dev/null +++ b/code/03/03.2/03.2-05-log-prefix.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os" +) + +func main() { + + a, b := 10, 20 + + // New logger will output to stdout with prefix "Log1: " and flags + // Note the space in prefix + myLog := log.New(os.Stdout, "Log1: ", log.Ldate|log.Lshortfile) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} diff --git a/code/03/03.2/log1.txt b/code/03/03.2/log1.txt new file mode 100755 index 0000000..a77b342 --- /dev/null +++ b/code/03/03.2/log1.txt @@ -0,0 +1,3 @@ +Use Print to log. +Ditto for Println. +Use Printf for format strings. 10 + 20 = 30 diff --git a/code/03/03.2/log2.txt b/code/03/03.2/log2.txt new file mode 100755 index 0000000..a77b342 --- /dev/null +++ b/code/03/03.2/log2.txt @@ -0,0 +1,3 @@ +Use Print to log. +Ditto for Println. +Use Printf for format strings. 10 + 20 = 30 diff --git a/content/03.0.md b/content/03.0.md index 3446064..2f88432 100755 --- a/content/03.0.md +++ b/content/03.0.md @@ -3,5 +3,5 @@ This is where this repo is going to divert a bit from BH books. This section is As I move forward and learn more, I will return and add more tutorials here. -- [03.1 - flag](03.1.md) - +- [03.1 - flag package](03.1.md): Parsing command line parameters. +- [03.2 - log package](03.2.md): Logging. diff --git a/content/03.1.md b/content/03.1.md index 2a4dbe6..84b0d2f 100755 --- a/content/03.1.md +++ b/content/03.1.md @@ -1,4 +1,4 @@ -# flag +# flag package [flag package][flag-pkg] is the Go equivalent of Python [argparse][python-argparse]. While not as powerful, it does what we expect it to do. It simplifies adding and parsing command line parameters, leaving us to concentrate on the tools. Most of our tools will need them to be actually useful (hardcoding URLs and IPs get old too fast). @@ -510,10 +510,10 @@ As you can see there's a lot of manual work in sub commands and they are not as -[flag-pkg]: https://golang.org/pkg/flag/ +[flag-pkg]: https://godoc.org/flag [python-argparse]: https://docs.python.org/2/howto/argparse.html -[flag-value-interface]: https://golang.org/pkg/flag/#Value +[flag-value-interface]: https://godoc.org/flag#Value [sync-waitgroup]: 02.6.md#syncwaitgroup -[flag-newflagset]: https://golang.org/pkg/flag/#NewFlagSet +[flag-newflagset]: https://godoc.org/flag#NewFlagSet [cobra-github]: https://github.com/spf13/cobra [cli-github]: https://github.com/urfave/cli diff --git a/content/03.2.md b/content/03.2.md new file mode 100755 index 0000000..a7f109b --- /dev/null +++ b/content/03.2.md @@ -0,0 +1,305 @@ +# log package +[log package][log-pkg] is used for logging. The examples (unlike some other packages) are not very helpful. It's very bare bones and has only two logging levels. + +For anything complicated use Google's [glog][glog-pkg] package. + + + +- [Basic logging](#basic-logging) +- [Custom logger](#custom-logger) + - [Log to file](#log-to-file) + - [Logging to multiple files/streams](#logging-to-multiple-filesstreams) + - [Flag](#flag) + - [Prefix](#prefix) +- [Logging levels](#logging-levels) + + + + + +## Basic logging +Basic logging is similar to other languages. + +``` go +// 03.2-01-basic-logging.go +package main + +import ( + "log" +) + +func main() { + + a, b := 10, 20 + + log.Print("Use Print to log.") + log.Println("Ditto for Println.") + log.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} +``` + +Each log is on a new line: + +``` +$ go run 03.2-01-basic-logging.go +2017/12/25 22:18:38 Use Print to log. +2017/12/25 22:18:38 Ditto for Println. +2017/12/25 22:18:38 Use Printf and format strings. 10 + 20 = 30 +``` + +We can also forward the output to a file (or any number of `io.Writer`s) with [log.SetOutput][setoutput1-log-pkg]. + +``` go + +logFile, err := os.Create("log1.txt") +if err != nil { + panic("Could not open file") +} + +log.SetOutput(logFile) +``` + +## Custom logger +We can setup a custom logger with [logger.New][new-log-pkg]. + +``` go +func New(out io.Writer, prefix string, flag int) *Logger +``` + +- `out`: Log destination. Any `io.Writer` like files. +- `prefix`: Appears before each log entry. Think `Warning/Info/Error`. +- `flag`: Defines logging properties (e.g. the date time format). + + +### Log to file +Using `out` we can log to files. + +``` go +// 03.2-02-log-file.go +package main + +import ( + "log" + "os" +) + +func main() { + + // Create a file + logFile, err := os.Create("log1.txt") + if err != nil { + panic("Could not open file") + } + + // Close the file after main returns + defer logFile.Close() + + a, b := 10, 20 + + // We will not use the other options + myLog := log.New(logFile, "", 0) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} +``` + +`log1.txt` will contain: + +``` +Use Print to log. +Ditto for Println. +Use Printf and format strings. 10 + 20 = 30 +``` + +After `New`, `mylog.SetOutput(w io.Writer)` can redirect the logger. + + +#### Logging to multiple files/streams +It's also possible to log to multiple files (or `io.Writer`s) with [io.MultiWriter][multiwriter-io-pkg]. This is useful when we want to both output to stdout and to files. + +``` go +// 03.2-03-log-multiple-files.go +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "os" +) + +func main() { + + // Create a file + logFile, err := os.Create("log1.txt") + if err != nil { + panic("Could not open file") + } + + // Close the file after main returns + defer logFile.Close() + + // Create a second file + logFile2, err := os.Create("log2.txt") + if err != nil { + panic("Could not open file2") + } + + defer logFile2.Close() + + // Create a buffer + var buflog bytes.Buffer + + multiW := io.MultiWriter(logFile, logFile2, &buflog, os.Stdout) + + a, b := 10, 20 + + // Log to multiW + myLog := log.New(multiW, "", 0) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) + + // Print buffer + fmt.Println("Buffer:") + fmt.Println(buflog.String()) +} +``` + +We can see what we logged in both stdout and buffer: + +``` +$ go run 03.2-03-log-multiple-files.go +Use Print to log. +Ditto for Println. +Use Printf and format strings. 10 + 20 = 30 +Buffer: +Use Print to log. +Ditto for Println. +Use Printf and format strings. 10 + 20 = 30 +``` + + +### Flag +Prefix should be next but by discussing flag we can see if it appears before flag format or not. `flag` is an integer and is a collection of bits (like `FLAGS` CPU register). The flags are defined as constants: + +``` go +// https://godoc.org/log#pkg-constants + +const ( + // Bits or'ed together to control what's printed. + // There is no control over the order they appear (the order listed + // here) or the format they present (as described in the comments). + // The prefix is followed by a colon only when Llongfile or Lshortfile + // is specified. + // For example, flags Ldate | Ltime (or LstdFlags) produce, + // 2009/01/23 01:23:23 message + // while flags Ldate | Ltime | Lmicroseconds | Llongfile produce, + // 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message + Ldate = 1 << iota // the date in the local time zone: 2009/01/23 + Ltime // the time in the local time zone: 01:23:23 + Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. + Llongfile // full file name and line number: /a/b/c/d.go:23 + Lshortfile // final file name element and line number: d.go:23. overrides Llongfile + LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone + LstdFlags = Ldate | Ltime // initial values for the standard logger +) +``` + +There's only room for a few bits of customization (see what I did there?). + +``` go +// 03.2-04-log-flags.go +package main + +import ( + "log" + "os" +) + +func main() { + + a, b := 10, 20 + + // New logger will output to stdout with flags + // Only log date and file + myLog := log.New(os.Stdout, "", log.Ldate|log.Lshortfile) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} +``` + +We log date and filename: + + +``` +$ go run 03.2-04-log-flags.go +2017/12/26 03.2-04-log-flags.go:25: Use Print to log. +2017/12/26 03.2-04-log-flags.go:26: Ditto for Println. +2017/12/26 03.2-04-log-flags.go:27: Use Printf and format strings. 10 + 20 = 30 +``` + + +### Prefix +`prefix` adds a string to the beginning of each log line. + +``` go +// 03.2-05-log-prefix.go +package main + +import ( + "log" + "os" +) + +func main() { + + a, b := 10, 20 + + // New logger will output to stdout with prefix "Log1: " and flags + // Note the space in prefix + myLog := log.New(os.Stdout, "Log1: ", log.Ldate|log.Lshortfile) + + myLog.Print("Use Print to log.") + myLog.Println("Ditto for Println.") + myLog.Printf("Use Printf and format strings. %d + %d = %d", a, b, a+b) +} +``` + +Prefix is printed before flags: + +``` +$ go run 03.2-05-log-prefix.go +Log1: 2017/12/26 03.2-05-log-prefix.go:16: Use Print to log. +Log1: 2017/12/26 03.2-05-log-prefix.go:17: Ditto for Println. +Log1: 2017/12/26 03.2-05-log-prefix.go:18: Use Printf and format strings. 10 + 20 = 30 +``` + + +## Logging levels +`log` only supports two logging levels: + +- [Fatal][fatal-log-pkg]: `log.Print` and calls `os.Exit(1)`. +- [Panic][panic-log-pkg]: `log.Print` and calls `panic()`. + +Both of these support `ln` and `f` variants (e.g. `Fatalf`, `Panicln`). + + + + + + +[log-pkg]: https://godoc.org/log +[glog-pkg]: https://godoc.org/github.com/golang/glog +[new-log-pkg]: https://godoc.org/log#New +[multiwriter-io-pkg]: https://godoc.org/io#MultiWriter +[setoutpu1-log-pkg]: https://godoc.org/log#SetOutput +[setoutput2-log-pkg]: https://godoc.org/log#Logger.SetOutput +[fatal-log-pkg]: https://godoc.org/log#Fatal +[panic-log-pkg]: https://godoc.org/log#Panic \ No newline at end of file