pax_global_header00006660000000000000000000000064140331161340014506gustar00rootroot0000000000000052 comment=c43594ee8944c916278e366b201b91434e882233 babylogger-1.2.0/000077500000000000000000000000001403311613400136235ustar00rootroot00000000000000babylogger-1.2.0/.gitignore000066400000000000000000000000231403311613400156060ustar00rootroot00000000000000.DS_Store vendor/* babylogger-1.2.0/LICENSE000066400000000000000000000020561403311613400146330ustar00rootroot00000000000000MIT License Copyright (c) 2018 Magic Numbers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. babylogger-1.2.0/README.md000066400000000000000000000032011403311613400150760ustar00rootroot00000000000000Babylogger ========== [![GoDoc Badge](https://godoc.org/github.com/magicnumbers/babylogger?status.svg)](https://pkg.go.dev/github.com/meowgorithm/babylogger) A Go HTTP logger middleware, for babies. ![Example image of Babylogger doing its logging](https://i.imgur.com/VGg7Wl6.png "Babylogger doing its logging") We’ve used it with [Goji][goji] and the Go standard library, but it should work with any multiplexer worth its salt. And by that we mean any multiplexer compatible with the standard library. Note that ANSI escape sequences (read: colors) will be stripped from the output when the logger is not running in a terminal. For example, log files won't contain any sort of ANSI intended for color output. Also note that for accurate response time logging Babylogger should be the first middleware called. ## Examples ### Standard Library ```go package main import ( "fmt" "net/http" "github.com/magicnumbers/babylogger" ) func main() { http.Handle("/", babylogger.Middleware(http.HandlerFunc(handler))) http.ListenAndServe(":8000", nil) } handler(w http.ResponseWriter, r *http.Request) { fmt.FPrintln(w, "Oh, hi, I didn’t see you there.") } ``` ### Goji ```go import ( "fmt" "net/http" "github.com/magicnumbers/babylogger" "goji.io" "goji.io/pat" ) func main() { mux := goji.NewMux() mux.Use(babylogger.Middleware) mux.HandleFunc(pat.Get("/"), handler) http.ListenAndServe(":8000", mux) } handler(w http.ResponseWriter, r *http.Request) { fmt.FPrintln(w, "Oh hi, I didn’t see you there.") } ``` ## License MIT [goji]: http://goji.io [mattn]: https://github.com/mattn babylogger-1.2.0/babylogger.go000066400000000000000000000122351403311613400162720ustar00rootroot00000000000000// Package babylogger is a simple HTTP logging middleware. It works with any // multiplexer compatible with the Go standard library. // // When a terminal is present it will log using nice colors. When the output is // not in a terminal (for example in logs) ANSI escape sequences (read: colors) // will be stripped from the output. // // Also note that for accurate response time logging Babylogger should be the // first middleware called. // // Windows support is not currently implemented, however it would be trivial // enough with the help of a couple packages from Mattn: // http://github.com/mattn/go-isatty and https://github.com/mattn/go-colorable // // Example using the standard library: // // package main // // import ( // "fmt" // "net/http" // "github.com/meowgorithm/babylogger" // ) // // func main() { // http.Handle("/", babylogger.Middleware(http.HandlerFunc(handler))) // http.ListenAndServe(":8000", nil) // } // // handler(w http.ResponseWriter, r *http.Request) { // fmt.FPrintln(w, "Oh, hi, I didn’t see you there.") // } // // Example with Goji: // // import ( // "fmt" // "net/http" // "github.com/meowgorithm/babylogger" // "goji.io" // "goji.io/pat" // ) // // func main() { // mux := goji.NewMux() // mux.Use(babylogger.Middleware) // mux.HandleFunc(pat.Get("/"), handler) // http.ListenAndServe(":8000", mux) // } // // handler(w http.ResponseWriter, r *http.Request) { // fmt.FPrintln(w, "Oh hi, I didn’t see you there.") // } package babylogger import ( "bufio" "fmt" "log" "net" "net/http" "strings" "time" "github.com/charmbracelet/lipgloss" humanize "github.com/dustin/go-humanize" ) // Styles. var ( timeStyle = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "240", Dark: "240"}) uriStyle = timeStyle.Copy() methodStyle = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "62", Dark: "62"}) http200Style = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "35", Dark: "48"}) http300Style = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "208", Dark: "192"}) http400Style = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "39", Dark: "86"}) http500Style = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "203", Dark: "204"}) subtleStyle = lipgloss.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "250"}) addressStyle = subtleStyle.Copy() ) type logWriter struct { http.ResponseWriter code, bytes int } func (r *logWriter) Write(p []byte) (int, error) { written, err := r.ResponseWriter.Write(p) r.bytes += written return written, err } // Note this is generally only called when sending an HTTP error, so it's // important to set the `code` value to 200 as a default func (r *logWriter) WriteHeader(code int) { r.code = code r.ResponseWriter.WriteHeader(code) } // Hijack exposes the underlying ResponseWriter Hijacker implementation for // WebSocket compatibility func (r *logWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hj, ok := r.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("WebServer does not support hijacking") } return hj.Hijack() } // Middleware is the logging middleware where we log incoming and outgoing // requests for a multiplexer. It should be the first middleware called so it // can log request times accurately. func Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { addr := r.RemoteAddr if colon := strings.LastIndex(addr, ":"); colon != -1 { addr = addr[:colon] } arrow := subtleStyle.Render("<-") method := methodStyle.Render(r.Method) uri := uriStyle.Render(r.RequestURI) address := addressStyle.Render(addr) // Log request log.Printf("%s %s %s %s", arrow, method, uri, address) writer := &logWriter{ ResponseWriter: w, code: http.StatusOK, // default. so important! see above. } arrow = subtleStyle.Render("->") startTime := time.Now() // Not sure why the request could possibly be nil, but it has happened if r == nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) writer.code = http.StatusInternalServerError } else { next.ServeHTTP(writer, r) } elapsedTime := time.Now().Sub(startTime) var statusStyle lipgloss.Style if writer.code < 300 { // 200s statusStyle = http200Style } else if writer.code < 400 { // 300s statusStyle = http300Style } else if writer.code < 500 { // 400s statusStyle = http400Style } else { // 500s statusStyle = http500Style } status := statusStyle.Render(fmt.Sprintf("%d %s", writer.code, http.StatusText(writer.code))) // The excellent humanize package adds a space between the integer and // the unit as far as bytes are conerned (105 B). In our case that // makes it a little harder on the eyes when scanning the logs, so // we're stripping that space formattedBytes := strings.Replace( humanize.Bytes(uint64(writer.bytes)), " ", "", 1) bytes := subtleStyle.Render(formattedBytes) time := timeStyle.Render(fmt.Sprintf("%s", elapsedTime)) // Log response log.Printf("%s %s %s %v", arrow, status, bytes, time) }) } babylogger-1.2.0/example/000077500000000000000000000000001403311613400152565ustar00rootroot00000000000000babylogger-1.2.0/example/main.go000066400000000000000000000016511403311613400165340ustar00rootroot00000000000000package main import ( "net/http" "github.com/meowgorithm/babylogger" ) func main() { // HTTP server with Babylogger middleware http.Handle("/", babylogger.Middleware(http.HandlerFunc(handler))) go func() { http.ListenAndServe(":1337", nil) }() // Perform some example HTTP requests, then exit h := "http://localhost:1337" c := &http.Client{} c.Get(h + "/") r, _ := http.NewRequest("POST", h+"/meow", nil) c.Do(r) r, _ = http.NewRequest("PUT", h+"/purr", nil) c.Do(r) c.Get(h + "/schnurr") } func handler(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { case "/": w.WriteHeader(http.StatusOK) w.Write([]byte("oh hey")) case "/meow": w.WriteHeader(http.StatusTemporaryRedirect) w.Write([]byte("it's over there")) case "/purr": w.WriteHeader(http.StatusNotFound) w.Write([]byte("nope, not here")) default: w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("ouch")) } } babylogger-1.2.0/go.mod000066400000000000000000000002171403311613400147310ustar00rootroot00000000000000module github.com/meowgorithm/babylogger require ( github.com/charmbracelet/lipgloss v0.1.1 github.com/dustin/go-humanize v1.0.0 ) go 1.13 babylogger-1.2.0/go.sum000066400000000000000000000035031403311613400147570ustar00rootroot00000000000000github.com/charmbracelet/lipgloss v0.1.1 h1:VdEOZxalvY8me9axyGz3grJF/1e6FY4Xy9/VwqQ/ctE= github.com/charmbracelet/lipgloss v0.1.1/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/termenv v0.8.1 h1:9q230czSP3DHVpkaPDXGp0TOfAwyjyYwXlUCQxQSaBk= github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=