OSDN Git Service

feat(version): update version to 1.1.0
[bytom/bytom.git] / log / log.go
index fda1b64..f616c87 100644 (file)
-// Package log implements a standard convention for structured logging.
-// Log entries are formatted as K=V pairs.
-// By default, output is written to stdout; this can be changed with SetOutput.
 package log
 
 import (
-       "context"
        "fmt"
-       "io"
+       "io/ioutil"
        "os"
-       "runtime"
-       "strconv"
+       "path/filepath"
        "strings"
        "sync"
        "time"
 
-       "chain/errors"
-)
-
-const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
-
-// context key type
-type key int
-
-var (
-       logWriterMu sync.Mutex // protects the following
-       logWriter   io.Writer  = os.Stdout
-       procPrefix  []byte     // process-global prefix; see SetPrefix vs AddPrefixkv
+       rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+       "github.com/sirupsen/logrus"
 
-       // pairDelims contains a list of characters that may be used as delimeters
-       // between key-value pairs in a log entry. Keys and values will be quoted or
-       // otherwise formatted to ensure that key-value extraction is unambiguous.
-       //
-       // The list of pair delimiters follows Splunk conventions, described here:
-       // http://answers.splunk.com/answers/143368/default-delimiters-for-key-value-extraction.html
-       pairDelims      = " ,;|&\t\n\r"
-       illegalKeyChars = pairDelims + `="`
-
-       // context key for log line prefixes
-       prefixKey key = 0
+       "github.com/bytom/bytom/config"
 )
 
-// Conventional key names for log entries
 const (
-       KeyCaller = "at" // location of caller
-       KeyTime   = "t"  // time of call
-
-       KeyMessage = "message" // produced by Message
-       KeyError   = "error"   // produced by Error
-       KeyStack   = "stack"   // used by Printkv to print stack on subsequent lines
-
-       keyLogError = "log-error" // for errors produced by the log package itself
+       rotationTime int64 = 86400
+       maxAge       int64 = 604800
 )
 
-// SetOutput sets the log output to w.
-// If SetOutput hasn't been called,
-// the default behavior is to write to stdout.
-func SetOutput(w io.Writer) {
-       logWriterMu.Lock()
-       logWriter = w
-       logWriterMu.Unlock()
-}
+var defaultFormatter = &logrus.TextFormatter{DisableColors: true}
 
-func appendPrefix(b []byte, keyval ...interface{}) []byte {
-       // Invariant: len(keyval) is always even.
-       if len(keyval)%2 != 0 {
-               panic(fmt.Sprintf("odd-length prefix args: %v", keyval))
+func InitLogFile(config *config.Config) error {
+       logPath := config.LogDir()
+       if err := clearLockFiles(logPath); err != nil {
+               return err
        }
-       for i := 0; i < len(keyval); i += 2 {
-               k := formatKey(keyval[i])
-               v := formatValue(keyval[i+1])
-               b = append(b, k...)
-               b = append(b, '=')
-               b = append(b, v...)
-               b = append(b, ' ')
-       }
-       return b
-}
 
-// SetPrefix sets the global output prefix.
-func SetPrefix(keyval ...interface{}) {
-       b := appendPrefix(nil, keyval...)
-       logWriterMu.Lock()
-       procPrefix = b
-       logWriterMu.Unlock()
+       hook := newBtmHook(logPath)
+       logrus.AddHook(hook)
+       logrus.SetOutput(ioutil.Discard) //控制台不输出
+       fmt.Printf("all logs are output in the %s directory\n", logPath)
+       return nil
 }
 
-// AddPrefixkv appends keyval to any prefix stored in ctx,
-// and returns a new context with the longer prefix.
-func AddPrefixkv(ctx context.Context, keyval ...interface{}) context.Context {
-       p := appendPrefix(prefix(ctx), keyval...)
-       // Note: subsequent calls will append to p, so set cap(p) here.
-       // See TestAddPrefixkvAppendTwice.
-       p = p[0:len(p):len(p)]
-       return context.WithValue(ctx, prefixKey, p)
+type BtmHook struct {
+       logPath string
+       lock    *sync.Mutex
 }
 
-func prefix(ctx context.Context) []byte {
-       b, _ := ctx.Value(prefixKey).([]byte)
-       return b
+func newBtmHook(logPath string) *BtmHook {
+       hook := &BtmHook{lock: new(sync.Mutex)}
+       hook.logPath = logPath
+       return hook
 }
 
-// Printkv prints a structured log entry to stdout. Log fields are
-// specified as a variadic sequence of alternating keys and values.
-//
-// Duplicate keys will be preserved.
-//
-// Two fields are automatically added to the log entry: t=[time]
-// and at=[file:line] indicating the location of the caller.
-// Use SkipFunc to prevent helper functions from showing up in the
-// at=[file:line] field.
-//
-// Printkv will also print the stack trace, if any, on separate lines
-// following the message. The stack is obtained from the following,
-// in order of preference:
-//   - a KeyStack value with type []byte or []errors.StackFrame
-//   - a KeyError value with type error, using the result of errors.Stack
-func Printkv(ctx context.Context, keyvals ...interface{}) {
-       // Invariant: len(keyvals) is always even.
-       if len(keyvals)%2 != 0 {
-               keyvals = append(keyvals, "", keyLogError, "odd number of log params")
+// Write a log line to an io.Writer.
+func (hook *BtmHook) ioWrite(entry *logrus.Entry) error {
+       module := "general"
+       if data, ok := entry.Data["module"]; ok {
+               module = data.(string)
        }
 
-       t := time.Now().UTC()
-
-       // Prepend the log entry with auto-generated fields.
-       out := fmt.Sprintf(
-               "%s=%s %s=%s",
-               KeyCaller, caller(),
-               KeyTime, formatValue(t.Format(rfc3339NanoFixed)),
+       logPath := filepath.Join(hook.logPath, module)
+       writer, err := rotatelogs.New(
+               logPath+".%Y%m%d",
+               rotatelogs.WithMaxAge(time.Duration(maxAge)*time.Second),
+               rotatelogs.WithRotationTime(time.Duration(rotationTime)*time.Second),
        )
-
-       var stack interface{}
-       for i := 0; i < len(keyvals); i += 2 {
-               k := keyvals[i]
-               v := keyvals[i+1]
-               if k == KeyStack && isStackVal(v) {
-                       stack = v
-                       continue
-               }
-               if k == KeyError {
-                       if e, ok := v.(error); ok && stack == nil {
-                               stack = errors.Stack(errors.Wrap(e)) // wrap to ensure callstack
-                       }
-               }
-               out += " " + formatKey(k) + "=" + formatValue(v)
+       if err != nil {
+               return err
        }
 
-       logWriterMu.Lock()
-       logWriter.Write(procPrefix)
-       logWriter.Write(prefix(ctx))
-       logWriter.Write([]byte(out)) // ignore errors
-       logWriter.Write([]byte{'\n'})
-       writeRawStack(logWriter, stack)
-       logWriterMu.Unlock()
-}
-
-// Fatalkv is equivalent to Printkv() followed by a call to os.Exit(1).
-func Fatalkv(ctx context.Context, keyvals ...interface{}) {
-       Printkv(ctx, keyvals...)
-       os.Exit(1)
-}
-
-func writeRawStack(w io.Writer, v interface{}) {
-       switch v := v.(type) {
-       case []byte:
-               if len(v) > 0 {
-                       w.Write(v)
-                       w.Write([]byte{'\n'})
-               }
-       case []errors.StackFrame:
-               for _, s := range v {
-                       io.WriteString(w, s.String())
-                       w.Write([]byte{'\n'})
-               }
+       msg, err := defaultFormatter.Format(entry)
+       if err != nil {
+               return err
        }
-}
 
-func isStackVal(v interface{}) bool {
-       switch v.(type) {
-       case []byte:
-               return true
-       case []errors.StackFrame:
-               return true
+       if _, err = writer.Write(msg); err != nil {
+               return err
        }
-       return false
-}
 
-// Printf prints a log entry containing a message assigned to the
-// "message" key. Arguments are handled as in fmt.Printf.
-func Printf(ctx context.Context, format string, a ...interface{}) {
-       Printkv(ctx, KeyMessage, fmt.Sprintf(format, a...))
+       return writer.Close()
 }
 
-// Error prints a log entry containing an error message assigned to the
-// "error" key.
-// Optionally, an error message prefix can be included. Prefix arguments are
-// handled as in fmt.Print.
-func Error(ctx context.Context, err error, a ...interface{}) {
-       if len(a) > 0 && len(errors.Stack(err)) > 0 {
-               err = errors.Wrap(err, a...) // keep err's stack
-       } else if len(a) > 0 {
-               err = fmt.Errorf("%s: %s", fmt.Sprint(a...), err) // don't add a stack here
+func clearLockFiles(logPath string) error {
+       files, err := ioutil.ReadDir(logPath)
+       if os.IsNotExist(err) {
+               return nil
+       } else if err != nil {
+               return err
        }
-       Printkv(ctx, KeyError, err)
-}
 
-// formatKey ensures that the stringified key is valid for use in a
-// Splunk-style K=V format. It stubs out delimeter and quoter characters in
-// the key string with hyphens.
-func formatKey(k interface{}) string {
-       s := fmt.Sprint(k)
-       if s == "" {
-               return "?"
-       }
-
-       for _, c := range illegalKeyChars {
-               s = strings.Replace(s, string(c), "-", -1)
+       for _, file := range files {
+               if ok := strings.HasSuffix(file.Name(), "_lock"); ok {
+                       if err := os.Remove(filepath.Join(logPath, file.Name())); err != nil {
+                               return err
+                       }
+               }
        }
-
-       return s
+       return nil
 }
 
-// formatValue ensures that the stringified value is valid for use in a
-// Splunk-style K=V format. It quotes the string value if delimeter or quoter
-// characters are present in the value string.
-func formatValue(v interface{}) string {
-       s := fmt.Sprint(v)
-       if strings.ContainsAny(s, pairDelims) {
-               return strconv.Quote(s)
-       }
-       return s
+func (hook *BtmHook) Fire(entry *logrus.Entry) error {
+       hook.lock.Lock()
+       defer hook.lock.Unlock()
+       return hook.ioWrite(entry)
 }
 
-// RecoverAndLogError must be used inside a defer.
-func RecoverAndLogError(ctx context.Context) {
-       if err := recover(); err != nil {
-               const size = 64 << 10
-               buf := make([]byte, size)
-               buf = buf[:runtime.Stack(buf, false)]
-               Printkv(ctx,
-                       KeyMessage, "panic",
-                       KeyError, err,
-                       KeyStack, buf,
-               )
-       }
+// Levels returns configured log levels.
+func (hook *BtmHook) Levels() []logrus.Level {
+       return logrus.AllLevels
 }