package log import ( "fmt" "io" "net" "os" "reflect" "sync" "io/ioutil" "path/filepath" "regexp" "strings" "github.com/go-stack/stack" ) // Handler defines where and how log records are written. // A Logger prints its log records by writing to a Handler. // 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 } // countingWriter wraps a WriteCloser object in order to count the written bytes. type countingWriter struct { w io.WriteCloser // the wrapped object count uint // number of bytes written } // Write increments the byte counter by the number of bytes written. // Implements the WriteCloser interface. func (w *countingWriter) Write(p []byte) (n int, err error) { n, err = w.w.Write(p) w.count += uint(n) return n, err } // Close implements the WriteCloser interface. func (w *countingWriter) Close() error { return w.w.Close() } // prepFile opens the log file at the given path, and cuts off the invalid part // from the end, because the previous execution could have been finished by interruption. // Assumes that every line ended by '\n' contains a valid log record. func prepFile(path string) (*countingWriter, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0600) if err != nil { return nil, err } _, err = f.Seek(-1, io.SeekEnd) if err != nil { return nil, err } buf := make([]byte, 1) var cut int64 for { if _, err := f.Read(buf); err != nil { return nil, err } if buf[0] == '\n' { break } if _, err = f.Seek(-2, io.SeekCurrent); err != nil { return nil, err } cut++ } fi, err := f.Stat() if err != nil { return nil, err } ns := fi.Size() - cut if err = f.Truncate(ns); err != nil { return nil, err } return &countingWriter{w: f, count: uint(ns)}, nil } // RotatingFileHandler returns a handler which writes log records to file chunks // at the given path. When a file's size reaches the limit, the handler creates // a new file named after the timestamp of the first log record it will contain. func RotatingFileHandler(path string, limit uint, formatter Format) (Handler, error) { if err := os.MkdirAll(path, 0700); err != nil { return nil, err } files, err := ioutil.ReadDir(path) if err != nil { return nil, err } re := regexp.MustCompile(`\.log$`) last := len(files) - 1 for last >= 0 && (!files[last].Mode().IsRegular() || !re.MatchString(files[last].Name())) { last-- } var counter *countingWriter if last >= 0 && files[last].Size() < int64(limit) { // Open the last file, and continue to write into it until it's size reaches the limit. if counter, err = prepFile(filepath.Join(path, files[last].Name())); err != nil { return nil, err } } if counter == nil { counter = new(countingWriter) } h := StreamHandler(counter, formatter) return FuncHandler(func(r *Record) error { if counter.count > limit { counter.Close() counter.w = nil } if counter.w == nil { f, err := os.OpenFile( filepath.Join(path, fmt.Sprintf("%s.log", strings.Replace(r.Time.Format("060102150405.00"), ".", "", 1))), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600, ) if err != nil { return err } counter.w = f counter.count = 0 } return h.Log(r) }), 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", formatCall("%+n", r.Call)) return h.Log(r) }) } // This function is here to please go vet on Go < 1.8. func formatCall(format string, c stack.Call) string { return fmt.Sprintf(format, c) } // 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) } // 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 }) } // 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 } 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 } 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 }) } // Must 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)) }