gitee.com/sasukebo/go-micro/v4@v4.7.1/logger/default.go (about)

     1  package logger
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	dlog "gitee.com/sasukebo/go-micro/v4/debug/log"
    14  )
    15  
    16  func init() {
    17  	lvl, err := GetLevel(os.Getenv("MICRO_LOG_LEVEL"))
    18  	if err != nil {
    19  		lvl = InfoLevel
    20  	}
    21  
    22  	DefaultLogger = NewLogger(WithLevel(lvl))
    23  }
    24  
    25  type defaultLogger struct {
    26  	sync.RWMutex
    27  	opts Options
    28  }
    29  
    30  // Init (opts...) should only overwrite provided options
    31  func (l *defaultLogger) Init(opts ...Option) error {
    32  	for _, o := range opts {
    33  		o(&l.opts)
    34  	}
    35  	return nil
    36  }
    37  
    38  func (l *defaultLogger) String() string {
    39  	return "default"
    40  }
    41  
    42  func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
    43  	l.Lock()
    44  	nfields := make(map[string]interface{}, len(l.opts.Fields))
    45  	for k, v := range l.opts.Fields {
    46  		nfields[k] = v
    47  	}
    48  	l.Unlock()
    49  
    50  	for k, v := range fields {
    51  		nfields[k] = v
    52  	}
    53  
    54  	return &defaultLogger{opts: Options{
    55  		Level:           l.opts.Level,
    56  		Fields:          nfields,
    57  		Out:             l.opts.Out,
    58  		CallerSkipCount: l.opts.CallerSkipCount,
    59  		Context:         l.opts.Context,
    60  	}}
    61  }
    62  
    63  func copyFields(src map[string]interface{}) map[string]interface{} {
    64  	dst := make(map[string]interface{}, len(src))
    65  	for k, v := range src {
    66  		dst[k] = v
    67  	}
    68  	return dst
    69  }
    70  
    71  // logCallerfilePath returns a package/file:line description of the caller,
    72  // preserving only the leaf directory name and file name.
    73  func logCallerfilePath(loggingFilePath string) string {
    74  	// To make sure we trim the path correctly on Windows too, we
    75  	// counter-intuitively need to use '/' and *not* os.PathSeparator here,
    76  	// because the path given originates from Go stdlib, specifically
    77  	// runtime.Caller() which (as of Mar/17) returns forward slashes even on
    78  	// Windows.
    79  	//
    80  	// See https://github.com/golang/go/issues/3335
    81  	// and https://github.com/golang/go/issues/18151
    82  	//
    83  	// for discussion on the issue on Go side.
    84  	idx := strings.LastIndexByte(loggingFilePath, '/')
    85  	if idx == -1 {
    86  		return loggingFilePath
    87  	}
    88  	idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
    89  	if idx == -1 {
    90  		return loggingFilePath
    91  	}
    92  	return loggingFilePath[idx+1:]
    93  }
    94  
    95  func (l *defaultLogger) Log(level Level, v ...interface{}) {
    96  	// TODO decide does we need to write message if log level not used?
    97  	if !l.opts.Level.Enabled(level) {
    98  		return
    99  	}
   100  
   101  	l.RLock()
   102  	fields := copyFields(l.opts.Fields)
   103  	l.RUnlock()
   104  
   105  	fields["level"] = level.String()
   106  
   107  	if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
   108  		fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
   109  	}
   110  
   111  	rec := dlog.Record{
   112  		Timestamp: time.Now(),
   113  		Message:   fmt.Sprint(v...),
   114  		Metadata:  make(map[string]string, len(fields)),
   115  	}
   116  
   117  	keys := make([]string, 0, len(fields))
   118  	for k, v := range fields {
   119  		keys = append(keys, k)
   120  		rec.Metadata[k] = fmt.Sprintf("%v", v)
   121  	}
   122  
   123  	sort.Strings(keys)
   124  	metadata := ""
   125  
   126  	for _, k := range keys {
   127  		metadata += fmt.Sprintf(" %s=%v", k, fields[k])
   128  	}
   129  
   130  	dlog.DefaultLog.Write(rec)
   131  
   132  	t := rec.Timestamp.Format("2006-01-02 15:04:05")
   133  	fmt.Printf("%s %s %v\n", t, metadata, rec.Message)
   134  }
   135  
   136  func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
   137  	//	 TODO decide does we need to write message if log level not used?
   138  	if level < l.opts.Level {
   139  		return
   140  	}
   141  
   142  	l.RLock()
   143  	fields := copyFields(l.opts.Fields)
   144  	l.RUnlock()
   145  
   146  	fields["level"] = level.String()
   147  
   148  	if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
   149  		fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
   150  	}
   151  
   152  	rec := dlog.Record{
   153  		Timestamp: time.Now(),
   154  		Message:   fmt.Sprintf(format, v...),
   155  		Metadata:  make(map[string]string, len(fields)),
   156  	}
   157  
   158  	keys := make([]string, 0, len(fields))
   159  	for k, v := range fields {
   160  		keys = append(keys, k)
   161  		rec.Metadata[k] = fmt.Sprintf("%v", v)
   162  	}
   163  
   164  	sort.Strings(keys)
   165  	metadata := ""
   166  
   167  	for _, k := range keys {
   168  		metadata += fmt.Sprintf(" %s=%v", k, fields[k])
   169  	}
   170  
   171  	dlog.DefaultLog.Write(rec)
   172  
   173  	t := rec.Timestamp.Format("2006-01-02 15:04:05")
   174  	fmt.Printf("%s %s %v\n", t, metadata, rec.Message)
   175  }
   176  
   177  func (l *defaultLogger) Options() Options {
   178  	// not guard against options Context values
   179  	l.RLock()
   180  	opts := l.opts
   181  	opts.Fields = copyFields(l.opts.Fields)
   182  	l.RUnlock()
   183  	return opts
   184  }
   185  
   186  // NewLogger builds a new logger based on options
   187  func NewLogger(opts ...Option) Logger {
   188  	// Default options
   189  	options := Options{
   190  		Level:           InfoLevel,
   191  		Fields:          make(map[string]interface{}),
   192  		Out:             os.Stderr,
   193  		CallerSkipCount: 2,
   194  		Context:         context.Background(),
   195  	}
   196  
   197  	l := &defaultLogger{opts: options}
   198  	if err := l.Init(opts...); err != nil {
   199  		l.Log(FatalLevel, err)
   200  	}
   201  
   202  	return l
   203  }