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