123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- package logx
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "path"
- "runtime"
- "runtime/debug"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "time"
- "github.com/tal-tech/go-zero/core/iox"
- "github.com/tal-tech/go-zero/core/sysx"
- "github.com/tal-tech/go-zero/core/timex"
- )
- const (
- // InfoLevel logs everything
- InfoLevel = iota
- // ErrorLevel includes errors, slows, stacks
- ErrorLevel
- // SevereLevel only log severe messages
- SevereLevel
- )
- const (
- timeFormat = "2006-01-02T15:04:05.000Z07"
- accessFilename = "access.log"
- errorFilename = "error.log"
- severeFilename = "severe.log"
- slowFilename = "slow.log"
- statFilename = "stat.log"
- consoleMode = "console"
- volumeMode = "volume"
- levelAlert = "alert"
- levelInfo = "info"
- levelError = "error"
- levelSevere = "severe"
- levelFatal = "fatal"
- levelSlow = "slow"
- levelStat = "stat"
- backupFileDelimiter = "-"
- callerInnerDepth = 5
- flags = 0x0
- )
- var (
- // ErrLogPathNotSet is an error that indicates the log path is not set.
- ErrLogPathNotSet = errors.New("log path must be set")
- // ErrLogNotInitialized is an error that log is not initialized.
- ErrLogNotInitialized = errors.New("log not initialized")
- // ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
- ErrLogServiceNameNotSet = errors.New("log service name must be set")
- writeConsole bool
- logLevel uint32
- infoLog io.WriteCloser
- errorLog io.WriteCloser
- severeLog io.WriteCloser
- slowLog io.WriteCloser
- statLog io.WriteCloser
- stackLog io.Writer
- once sync.Once
- initialized uint32
- options logOptions
- )
- type (
- logEntry struct {
- Timestamp string `json:"@timestamp"`
- Level string `json:"level"`
- Duration string `json:"duration,omitempty"`
- Content string `json:"content"`
- }
- logOptions struct {
- gzipEnabled bool
- logStackCooldownMills int
- keepDays int
- }
- // LogOption defines the method to customize the logging.
- LogOption func(options *logOptions)
- // A Logger represents a logger.
- Logger interface {
- Error(...interface{})
- Errorf(string, ...interface{})
- Info(...interface{})
- Infof(string, ...interface{})
- Slow(...interface{})
- Slowf(string, ...interface{})
- WithDuration(time.Duration) Logger
- }
- )
- // MustSetup sets up logging with given config c. It exits on error.
- func MustSetup(c LogConf) {
- Must(SetUp(c))
- }
- // SetUp sets up the logx. If already set up, just return nil.
- // we allow SetUp to be called multiple times, because for example
- // we need to allow different service frameworks to initialize logx respectively.
- // the same logic for SetUp
- func SetUp(c LogConf) error {
- switch c.Mode {
- case consoleMode:
- setupWithConsole(c)
- return nil
- case volumeMode:
- return setupWithVolume(c)
- default:
- return setupWithFiles(c)
- }
- }
- // Alert alerts v in alert level, and the message is written to error log.
- func Alert(v string) {
- output(errorLog, levelAlert, v)
- }
- // Close closes the logging.
- func Close() error {
- if writeConsole {
- return nil
- }
- if atomic.LoadUint32(&initialized) == 0 {
- return ErrLogNotInitialized
- }
- atomic.StoreUint32(&initialized, 0)
- if infoLog != nil {
- if err := infoLog.Close(); err != nil {
- return err
- }
- }
- if errorLog != nil {
- if err := errorLog.Close(); err != nil {
- return err
- }
- }
- if severeLog != nil {
- if err := severeLog.Close(); err != nil {
- return err
- }
- }
- if slowLog != nil {
- if err := slowLog.Close(); err != nil {
- return err
- }
- }
- if statLog != nil {
- if err := statLog.Close(); err != nil {
- return err
- }
- }
- return nil
- }
- // Disable disables the logging.
- func Disable() {
- once.Do(func() {
- atomic.StoreUint32(&initialized, 1)
- infoLog = iox.NopCloser(ioutil.Discard)
- errorLog = iox.NopCloser(ioutil.Discard)
- severeLog = iox.NopCloser(ioutil.Discard)
- slowLog = iox.NopCloser(ioutil.Discard)
- statLog = iox.NopCloser(ioutil.Discard)
- stackLog = ioutil.Discard
- })
- }
- // Error writes v into error log.
- func Error(v ...interface{}) {
- ErrorCaller(1, v...)
- }
- // Errorf writes v with format into error log.
- func Errorf(format string, v ...interface{}) {
- ErrorCallerf(1, format, v...)
- }
- // ErrorCaller writes v with context into error log.
- func ErrorCaller(callDepth int, v ...interface{}) {
- errorSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
- }
- // ErrorCallerf writes v with context in format into error log.
- func ErrorCallerf(callDepth int, format string, v ...interface{}) {
- errorSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth)
- }
- // ErrorStack writes v along with call stack into error log.
- func ErrorStack(v ...interface{}) {
- // there is newline in stack string
- stackSync(fmt.Sprint(v...))
- }
- // ErrorStackf writes v along with call stack in format into error log.
- func ErrorStackf(format string, v ...interface{}) {
- // there is newline in stack string
- stackSync(fmt.Sprintf(format, v...))
- }
- // Info writes v into access log.
- func Info(v ...interface{}) {
- infoSync(fmt.Sprint(v...))
- }
- // Infof writes v with format into access log.
- func Infof(format string, v ...interface{}) {
- infoSync(fmt.Sprintf(format, v...))
- }
- // Must checks if err is nil, otherwise logs the err and exits.
- func Must(err error) {
- if err != nil {
- msg := formatWithCaller(err.Error(), 3)
- log.Print(msg)
- output(severeLog, levelFatal, msg)
- os.Exit(1)
- }
- }
- // SetLevel sets the logging level. It can be used to suppress some logs.
- func SetLevel(level uint32) {
- atomic.StoreUint32(&logLevel, level)
- }
- // Severe writes v into severe log.
- func Severe(v ...interface{}) {
- severeSync(fmt.Sprint(v...))
- }
- // Severef writes v with format into severe log.
- func Severef(format string, v ...interface{}) {
- severeSync(fmt.Sprintf(format, v...))
- }
- // Slow writes v into slow log.
- func Slow(v ...interface{}) {
- slowSync(fmt.Sprint(v...))
- }
- // Slowf writes v with format into slow log.
- func Slowf(format string, v ...interface{}) {
- slowSync(fmt.Sprintf(format, v...))
- }
- // Stat writes v into stat log.
- func Stat(v ...interface{}) {
- statSync(fmt.Sprint(v...))
- }
- // Statf writes v with format into stat log.
- func Statf(format string, v ...interface{}) {
- statSync(fmt.Sprintf(format, v...))
- }
- // WithCooldownMillis customizes logging on writting call stack interval.
- func WithCooldownMillis(millis int) LogOption {
- return func(opts *logOptions) {
- opts.logStackCooldownMills = millis
- }
- }
- // WithKeepDays customizes logging to keep logs with days.
- func WithKeepDays(days int) LogOption {
- return func(opts *logOptions) {
- opts.keepDays = days
- }
- }
- // WithGzip customizes logging to automatically gzip the log files.
- func WithGzip() LogOption {
- return func(opts *logOptions) {
- opts.gzipEnabled = true
- }
- }
- func createOutput(path string) (io.WriteCloser, error) {
- if len(path) == 0 {
- return nil, ErrLogPathNotSet
- }
- return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
- options.gzipEnabled), options.gzipEnabled)
- }
- func errorSync(msg string, callDepth int) {
- if shouldLog(ErrorLevel) {
- outputError(errorLog, msg, callDepth)
- }
- }
- func formatWithCaller(msg string, callDepth int) string {
- var buf strings.Builder
- caller := getCaller(callDepth)
- if len(caller) > 0 {
- buf.WriteString(caller)
- buf.WriteByte(' ')
- }
- buf.WriteString(msg)
- return buf.String()
- }
- func getCaller(callDepth int) string {
- var buf strings.Builder
- _, file, line, ok := runtime.Caller(callDepth)
- if ok {
- short := file
- for i := len(file) - 1; i > 0; i-- {
- if file[i] == '/' {
- short = file[i+1:]
- break
- }
- }
- buf.WriteString(short)
- buf.WriteByte(':')
- buf.WriteString(strconv.Itoa(line))
- }
- return buf.String()
- }
- func getTimestamp() string {
- return timex.Time().Format(timeFormat)
- }
- func handleOptions(opts []LogOption) {
- for _, opt := range opts {
- opt(&options)
- }
- }
- func infoSync(msg string) {
- if shouldLog(InfoLevel) {
- output(infoLog, levelInfo, msg)
- }
- }
- func output(writer io.Writer, level, msg string) {
- info := logEntry{
- Timestamp: getTimestamp(),
- Level: level,
- Content: msg,
- }
- outputJson(writer, info)
- }
- func outputError(writer io.Writer, msg string, callDepth int) {
- content := formatWithCaller(msg, callDepth)
- output(writer, levelError, content)
- }
- func outputJson(writer io.Writer, info interface{}) {
- if content, err := json.Marshal(info); err != nil {
- log.Println(err.Error())
- } else if atomic.LoadUint32(&initialized) == 0 || writer == nil {
- log.Println(string(content))
- } else {
- writer.Write(append(content, '\n'))
- }
- }
- func setupLogLevel(c LogConf) {
- switch c.Level {
- case levelInfo:
- SetLevel(InfoLevel)
- case levelError:
- SetLevel(ErrorLevel)
- case levelSevere:
- SetLevel(SevereLevel)
- }
- }
- func setupWithConsole(c LogConf) {
- once.Do(func() {
- atomic.StoreUint32(&initialized, 1)
- writeConsole = true
- setupLogLevel(c)
- infoLog = newLogWriter(log.New(os.Stdout, "", flags))
- errorLog = newLogWriter(log.New(os.Stderr, "", flags))
- severeLog = newLogWriter(log.New(os.Stderr, "", flags))
- slowLog = newLogWriter(log.New(os.Stderr, "", flags))
- stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
- statLog = infoLog
- })
- }
- func setupWithFiles(c LogConf) error {
- var opts []LogOption
- var err error
- if len(c.Path) == 0 {
- return ErrLogPathNotSet
- }
- opts = append(opts, WithCooldownMillis(c.StackCooldownMillis))
- if c.Compress {
- opts = append(opts, WithGzip())
- }
- if c.KeepDays > 0 {
- opts = append(opts, WithKeepDays(c.KeepDays))
- }
- accessFile := path.Join(c.Path, accessFilename)
- errorFile := path.Join(c.Path, errorFilename)
- severeFile := path.Join(c.Path, severeFilename)
- slowFile := path.Join(c.Path, slowFilename)
- statFile := path.Join(c.Path, statFilename)
- once.Do(func() {
- atomic.StoreUint32(&initialized, 1)
- handleOptions(opts)
- setupLogLevel(c)
- if infoLog, err = createOutput(accessFile); err != nil {
- return
- }
- if errorLog, err = createOutput(errorFile); err != nil {
- return
- }
- if severeLog, err = createOutput(severeFile); err != nil {
- return
- }
- if slowLog, err = createOutput(slowFile); err != nil {
- return
- }
- if statLog, err = createOutput(statFile); err != nil {
- return
- }
- stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
- })
- return err
- }
- func setupWithVolume(c LogConf) error {
- if len(c.ServiceName) == 0 {
- return ErrLogServiceNameNotSet
- }
- c.Path = path.Join(c.Path, c.ServiceName, sysx.Hostname())
- return setupWithFiles(c)
- }
- func severeSync(msg string) {
- if shouldLog(SevereLevel) {
- output(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
- }
- }
- func shouldLog(level uint32) bool {
- return atomic.LoadUint32(&logLevel) <= level
- }
- func slowSync(msg string) {
- if shouldLog(ErrorLevel) {
- output(slowLog, levelSlow, msg)
- }
- }
- func stackSync(msg string) {
- if shouldLog(ErrorLevel) {
- output(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
- }
- }
- func statSync(msg string) {
- if shouldLog(InfoLevel) {
- output(statLog, levelStat, msg)
- }
- }
- type logWriter struct {
- logger *log.Logger
- }
- func newLogWriter(logger *log.Logger) logWriter {
- return logWriter{
- logger: logger,
- }
- }
- func (lw logWriter) Close() error {
- return nil
- }
- func (lw logWriter) Write(data []byte) (int, error) {
- lw.logger.Print(string(data))
- return len(data), nil
- }
|