aboutsummaryrefslogtreecommitdiffstats
path: root/log/handler.go
diff options
context:
space:
mode:
Diffstat (limited to 'log/handler.go')
-rw-r--r--log/handler.go356
1 files changed, 356 insertions, 0 deletions
diff --git a/log/handler.go b/log/handler.go
new file mode 100644
index 000000000..abb17b4c4
--- /dev/null
+++ b/log/handler.go
@@ -0,0 +1,356 @@
+package log
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "reflect"
+ "sync"
+
+ "github.com/go-stack/stack"
+)
+
+// A Logger prints its log records by writing to a Handler.
+// The Handler interface defines where and how log records are written.
+// Handlers are composable, providing you great flexibility in combining
+// them to achieve the logging structure that suits your applications.
+type Handler interface {
+ Log(r *Record) error
+}
+
+// FuncHandler returns a Handler that logs records with the given
+// function.
+func FuncHandler(fn func(r *Record) error) Handler {
+ return funcHandler(fn)
+}
+
+type funcHandler func(r *Record) error
+
+func (h funcHandler) Log(r *Record) error {
+ return h(r)
+}
+
+// StreamHandler writes log records to an io.Writer
+// with the given format. StreamHandler can be used
+// to easily begin writing log records to other
+// outputs.
+//
+// StreamHandler wraps itself with LazyHandler and SyncHandler
+// to evaluate Lazy objects and perform safe concurrent writes.
+func StreamHandler(wr io.Writer, fmtr Format) Handler {
+ h := FuncHandler(func(r *Record) error {
+ _, err := wr.Write(fmtr.Format(r))
+ return err
+ })
+ return LazyHandler(SyncHandler(h))
+}
+
+// SyncHandler can be wrapped around a handler to guarantee that
+// only a single Log operation can proceed at a time. It's necessary
+// for thread-safe concurrent writes.
+func SyncHandler(h Handler) Handler {
+ var mu sync.Mutex
+ return FuncHandler(func(r *Record) error {
+ defer mu.Unlock()
+ mu.Lock()
+ return h.Log(r)
+ })
+}
+
+// FileHandler returns a handler which writes log records to the give file
+// using the given format. If the path
+// already exists, FileHandler will append to the given file. If it does not,
+// FileHandler will create the file with mode 0644.
+func FileHandler(path string, fmtr Format) (Handler, error) {
+ f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ return nil, err
+ }
+ return closingHandler{f, StreamHandler(f, fmtr)}, nil
+}
+
+// NetHandler opens a socket to the given address and writes records
+// over the connection.
+func NetHandler(network, addr string, fmtr Format) (Handler, error) {
+ conn, err := net.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+
+ return closingHandler{conn, StreamHandler(conn, fmtr)}, nil
+}
+
+// XXX: closingHandler is essentially unused at the moment
+// it's meant for a future time when the Handler interface supports
+// a possible Close() operation
+type closingHandler struct {
+ io.WriteCloser
+ Handler
+}
+
+func (h *closingHandler) Close() error {
+ return h.WriteCloser.Close()
+}
+
+// CallerFileHandler returns a Handler that adds the line number and file of
+// the calling function to the context with key "caller".
+func CallerFileHandler(h Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call))
+ return h.Log(r)
+ })
+}
+
+// CallerFuncHandler returns a Handler that adds the calling function name to
+// the context with key "fn".
+func CallerFuncHandler(h Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ r.Ctx = append(r.Ctx, "fn", fmt.Sprintf("%+n", r.Call))
+ return h.Log(r)
+ })
+}
+
+// CallerStackHandler returns a Handler that adds a stack trace to the context
+// with key "stack". The stack trace is formated as a space separated list of
+// call sites inside matching []'s. The most recent call site is listed first.
+// Each call site is formatted according to format. See the documentation of
+// package github.com/go-stack/stack for the list of supported formats.
+func CallerStackHandler(format string, h Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ s := stack.Trace().TrimBelow(r.Call).TrimRuntime()
+ if len(s) > 0 {
+ r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s))
+ }
+ return h.Log(r)
+ })
+}
+
+// FilterHandler returns a Handler that only writes records to the
+// wrapped Handler if the given function evaluates true. For example,
+// to only log records where the 'err' key is not nil:
+//
+// logger.SetHandler(FilterHandler(func(r *Record) bool {
+// for i := 0; i < len(r.Ctx); i += 2 {
+// if r.Ctx[i] == "err" {
+// return r.Ctx[i+1] != nil
+// }
+// }
+// return false
+// }, h))
+//
+func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ if fn(r) {
+ return h.Log(r)
+ }
+ return nil
+ })
+}
+
+// MatchFilterHandler returns a Handler that only writes records
+// to the wrapped Handler if the given key in the logged
+// context matches the value. For example, to only log records
+// from your ui package:
+//
+// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
+//
+func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
+ return FilterHandler(func(r *Record) (pass bool) {
+ switch key {
+ case r.KeyNames.Lvl:
+ return r.Lvl == value
+ case r.KeyNames.Time:
+ return r.Time == value
+ case r.KeyNames.Msg:
+ return r.Msg == value
+ }
+
+ for i := 0; i < len(r.Ctx); i += 2 {
+ if r.Ctx[i] == key {
+ return r.Ctx[i+1] == value
+ }
+ }
+ return false
+ }, h)
+}
+
+// LvlFilterHandler returns a Handler that only writes
+// records which are less than the given verbosity
+// level to the wrapped Handler. For example, to only
+// log Error/Crit records:
+//
+// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
+//
+func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
+ return FilterHandler(func(r *Record) (pass bool) {
+ return r.Lvl <= maxLvl
+ }, h)
+}
+
+// A MultiHandler dispatches any write to each of its handlers.
+// This is useful for writing different types of log information
+// to different locations. For example, to log to a file and
+// standard error:
+//
+// log.MultiHandler(
+// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
+// log.StderrHandler)
+//
+func MultiHandler(hs ...Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ for _, h := range hs {
+ // what to do about failures?
+ h.Log(r)
+ }
+ return nil
+ })
+}
+
+// A FailoverHandler writes all log records to the first handler
+// specified, but will failover and write to the second handler if
+// the first handler has failed, and so on for all handlers specified.
+// For example you might want to log to a network socket, but failover
+// to writing to a file if the network fails, and then to
+// standard out if the file write fails:
+//
+// log.FailoverHandler(
+// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()),
+// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
+// log.StdoutHandler)
+//
+// All writes that do not go to the first handler will add context with keys of
+// the form "failover_err_{idx}" which explain the error encountered while
+// trying to write to the handlers before them in the list.
+func FailoverHandler(hs ...Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ var err error
+ for i, h := range hs {
+ err = h.Log(r)
+ if err == nil {
+ return nil
+ } else {
+ r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err)
+ }
+ }
+
+ return err
+ })
+}
+
+// ChannelHandler writes all records to the given channel.
+// It blocks if the channel is full. Useful for async processing
+// of log messages, it's used by BufferedHandler.
+func ChannelHandler(recs chan<- *Record) Handler {
+ return FuncHandler(func(r *Record) error {
+ recs <- r
+ return nil
+ })
+}
+
+// BufferedHandler writes all records to a buffered
+// channel of the given size which flushes into the wrapped
+// handler whenever it is available for writing. Since these
+// writes happen asynchronously, all writes to a BufferedHandler
+// never return an error and any errors from the wrapped handler are ignored.
+func BufferedHandler(bufSize int, h Handler) Handler {
+ recs := make(chan *Record, bufSize)
+ go func() {
+ for m := range recs {
+ _ = h.Log(m)
+ }
+ }()
+ return ChannelHandler(recs)
+}
+
+// LazyHandler writes all values to the wrapped handler after evaluating
+// any lazy functions in the record's context. It is already wrapped
+// around StreamHandler and SyslogHandler in this library, you'll only need
+// it if you write your own Handler.
+func LazyHandler(h Handler) Handler {
+ return FuncHandler(func(r *Record) error {
+ // go through the values (odd indices) and reassign
+ // the values of any lazy fn to the result of its execution
+ hadErr := false
+ for i := 1; i < len(r.Ctx); i += 2 {
+ lz, ok := r.Ctx[i].(Lazy)
+ if ok {
+ v, err := evaluateLazy(lz)
+ if err != nil {
+ hadErr = true
+ r.Ctx[i] = err
+ } else {
+ if cs, ok := v.(stack.CallStack); ok {
+ v = cs.TrimBelow(r.Call).TrimRuntime()
+ }
+ r.Ctx[i] = v
+ }
+ }
+ }
+
+ if hadErr {
+ r.Ctx = append(r.Ctx, errorKey, "bad lazy")
+ }
+
+ return h.Log(r)
+ })
+}
+
+func evaluateLazy(lz Lazy) (interface{}, error) {
+ t := reflect.TypeOf(lz.Fn)
+
+ if t.Kind() != reflect.Func {
+ return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
+ }
+
+ if t.NumIn() > 0 {
+ return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
+ }
+
+ if t.NumOut() == 0 {
+ return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
+ }
+
+ value := reflect.ValueOf(lz.Fn)
+ results := value.Call([]reflect.Value{})
+ if len(results) == 1 {
+ return results[0].Interface(), nil
+ } else {
+ values := make([]interface{}, len(results))
+ for i, v := range results {
+ values[i] = v.Interface()
+ }
+ return values, nil
+ }
+}
+
+// DiscardHandler reports success for all writes but does nothing.
+// It is useful for dynamically disabling logging at runtime via
+// a Logger's SetHandler method.
+func DiscardHandler() Handler {
+ return FuncHandler(func(r *Record) error {
+ return nil
+ })
+}
+
+// The Must object provides the following Handler creation functions
+// which instead of returning an error parameter only return a Handler
+// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
+var Must muster
+
+func must(h Handler, err error) Handler {
+ if err != nil {
+ panic(err)
+ }
+ return h
+}
+
+type muster struct{}
+
+func (m muster) FileHandler(path string, fmtr Format) Handler {
+ return must(FileHandler(path, fmtr))
+}
+
+func (m muster) NetHandler(network, addr string, fmtr Format) Handler {
+ return must(NetHandler(network, addr, fmtr))
+}