-// 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
}