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