Documentation
¶
Overview ¶
Package log implements an opinionated, structured logging API and data model. It's not actually a logger as it requires you to provide an Observer to actually handle log events, which defines what it means to "log" (print to stdout, write to disk, send to aggregator, etc).
Instead of providing nestable logger instances, you use the package-level API and it will determine the calling package for context. The calling package is added as a field to the Event produced by the call.
There are 4 types of log events you can create: Info, Debug, Local, Fatal. They are more semantic tags than log levels. Fatal is a special log event type that will cause the program to terminate non-zero after logging. Local and Debug are tied to modes.
Local is a mode that implies local development. Use Local for caveman debugging or temporary log events. Observers can choose to highlight these events so they stand out to make development and local debugging easier. In many cases, Local is unnecessary and you can just use Debug.
Debug is a mode that implies verbose logging for better debugging. This is often used with local development, but can also be enabled in certain deployments such as dev or staging. In some cases it might be turned on temporarily in production.
When you log with Local or Debug, whether their mode is enabled will determine if those log events will be processed. Otherwise, you log with Info, which is intended for useful log events and messages.
Info, Debug, Local, and Fatal all take an arbitrary number of arguments of any type. It's the job of the FieldProcessor to convert a value into appropriate key-value fields. These fields are added to an Event object with the type and timestamp.
This Event object is then passed to registered Observers. Observers can then print, or colorize and print, or send to a log aggregator, or detect certain errors and send to exception trackers, etc.
The package also comes with a wrapper for http.ResponseWriter to use this package for HTTP logging.
Example (Basic) ¶
Basic example shows a simple log observer and using the logging functions.
package main
import (
"fmt"
"strings"
"github.com/gliderlabs/comlab/pkg/log"
)
type LogPrinter struct{}
// This Observer iterates over the event Index to maintain order of fields,
// and creates a list of comma separated key-value pairs to print out.
func (lp LogPrinter) Log(event log.Event) {
var fields []string
for _, key := range event.Index {
fields = append(fields, fmt.Sprintf("%s=%s", key, event.Fields[key]))
}
fmt.Println(strings.Join(fields, ", "))
}
// Basic example shows a simple log observer and using the logging functions.
func main() {
log.RegisterObserver(LogPrinter{})
log.Info("Hello world", log.Fields{"foo": "bar"}, 12345)
}
Output: msg=Hello world, foo=bar, data=12345
Example (FieldProcessor) ¶
FieldProcessor example shows using the field processor callback to support new types.
package main
import (
"strconv"
"github.com/gliderlabs/comlab/pkg/log"
)
type CustomData struct {
Foo string
Bar string
}
func fieldProcessor(e log.Event, field interface{}) (log.Event, bool) {
switch obj := field.(type) {
case int:
return e.Append("num", strconv.Itoa(obj)), true
case CustomData:
e = e.Append("custom.foo", obj.Foo)
e = e.Append("custom.bar", obj.Bar)
return e, true
}
return e, false
}
// FieldProcessor example shows using the field processor callback to support new types.
func main() {
log.RegisterObserver(LogPrinter{})
log.SetFieldProcessor(fieldProcessor)
log.Info("Hello world", 12345, CustomData{"FOO", "BAR"})
}
Output: msg=Hello world, num=12345, custom.foo=FOO, custom.bar=BAR
Example (Http) ¶
HTTP example shows using the ResponseWriter wrapper for HTTP logging
package main
import (
"net/http"
"strconv"
"time"
"github.com/gliderlabs/comlab/pkg/log"
)
// our field processor will need to know how to process HTTP related types
func httpFieldProcessor(e log.Event, field interface{}) (log.Event, bool) {
switch obj := field.(type) {
case time.Duration:
return e.Append("dur", strconv.Itoa(int(obj/time.Millisecond))), true
case log.ResponseWriter:
e = e.Append("bytes", strconv.Itoa(obj.Size()))
e = e.Append("status", strconv.Itoa(obj.Status()))
return e, true
case *http.Request:
e = e.Append("ip", obj.RemoteAddr)
e = e.Append("method", obj.Method)
e = e.Append("path", obj.RequestURI)
return e, true
}
return e, false
}
// our logging middleware that injects a wrapped ResponseWriter
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
lw := log.WrapResponseWriter(w)
defer log.Info(r, lw, time.Now().Sub(t))
next.ServeHTTP(lw, r)
})
}
// HTTP example shows using the ResponseWriter wrapper for HTTP logging
func main() {
log.SetFieldProcessor(httpFieldProcessor)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("This is a catch-all route"))
})
// wrap our mux with our middleware
http.ListenAndServe(":8080", loggingMiddleware(mux))
}
Output:
Index ¶
- func Debug(o ...interface{})
- func Fatal(o ...interface{})
- func Info(o ...interface{})
- func Local(o ...interface{})
- func RegisterObserver(o Observer)
- func SetDebug(debug bool)
- func SetFieldProcessor(fn FieldProcessor)
- func SetLocal(local bool)
- func UnregisterObserver(o Observer)
- type Event
- type EventType
- type FieldProcessor
- type Fields
- type Observer
- type ResponseWriter
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Debug ¶
func Debug(o ...interface{})
Debug logs data intended to be more verbose for debugging and development. Debug only logs when debug mode is enabled.
func Fatal ¶
func Fatal(o ...interface{})
Fatal logs data that represents a fatal error. Fatal will exit with status code 1 after logging.
func Info ¶
func Info(o ...interface{})
Info logs data intended to be useful to all users in all modes.
func Local ¶
func Local(o ...interface{})
Local logs data intended to be seen during local development. Local only logs when local mode is enabled.
func RegisterObserver ¶
func RegisterObserver(o Observer)
RegisterObserver adds an Observer to receive processed log events
func SetFieldProcessor ¶
func SetFieldProcessor(fn FieldProcessor)
SetFieldProcessor lets you specify the field processing callback for your application to turn logged values into key-value fields for an event. See FieldProcessor for more information.
func UnregisterObserver ¶
func UnregisterObserver(o Observer)
UnregisterObserver removes an Observer from receiving log events
Types ¶
type Event ¶
type Event struct {
// EventType enum value
Type EventType
// Time of when the event was logged
Time time.Time
// Map of key-value fields
Fields Fields
// Order of fields added, by key
Index []string
}
Event represents a log event, which is what is given to registered Observers and the FieldProcessor. Observers use them to actually log, FieldProcessor typically adds fields with Append.
Events are treated as immutable so they are passed by value and all operations on them will return a modified copy.
type FieldProcessor ¶
FieldProcessor is the function signature for the callback expected by SetFieldProcessor. This callback is called for every unknown value passed to a logging function. It's given an Event to append to, the field value (an argument to Info, Debug, etc), and returns a new Event and whether or not it processed the field. Unprocessed fields will be cast to a string and appended to a field called "data", unless they are an error.
Fields that are errors are passed to the callback so you have a chance to process them into appropriate fields, but if you indicate it was not processed, it will create a field called "err" with the string value of its Error() method.
There are two types that are handled by the package and not passed to the FieldProcessor callback: strings and Fields. Fields are appended directly to the Event, and strings are always appended as a field called "msg". Multiple strings will be concatenated into the "msg" field in order.
type Fields ¶
Fields is shorthand for string map of strings used by Event for fields, but can also be used when logging to explicitly add key-values.
type Observer ¶
type Observer interface {
Log(e Event)
}
Observer is the interface of registerable log observers. They simply receive a fully processed Event via the Log method.
type ResponseWriter ¶
type ResponseWriter interface {
// contains filtered or unexported methods
}
ResponseWriter is the interface for wrapped http.ResponseWriters. Primarily it adds Status() and Size() methods (both returning ints).
func WrapResponseWriter ¶
func WrapResponseWriter(w http.ResponseWriter) ResponseWriter
WrapResponseWriter will take an http.ResponseWriter and return it wrapped as this package's ResponseWriter.