github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/logger/standard.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package logger
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    14  	logging "github.com/keybase/go-logging"
    15  	"golang.org/x/net/context"
    16  
    17  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    18  )
    19  
    20  const permDir os.FileMode = 0700
    21  
    22  // Map from module name to whether SetLevel() has been called for that
    23  // module.
    24  var initLoggingSetLevelCalled = map[string]struct{}{}
    25  
    26  // Protects access to initLoggingSetLevelCalled and the actual
    27  // SetLevel call.
    28  var initLoggingSetLevelMutex sync.Mutex
    29  
    30  // CtxStandardLoggerKey is a type defining context keys used by the
    31  // Standard logger.
    32  type CtxStandardLoggerKey int
    33  
    34  const (
    35  	// CtxLogTagsKey defines a context key that can associate with a map of
    36  	// context keys (key -> descriptive-name), the mapped values of which should
    37  	// be logged by a Standard logger if one of those keys is seen in a context
    38  	// during a log call.
    39  	CtxLogTagsKey CtxStandardLoggerKey = iota
    40  )
    41  
    42  type CtxLogTags map[interface{}]string
    43  
    44  // NewContext returns a new Context that carries adds the given log
    45  // tag mappings (context key -> display string).
    46  func NewContextWithLogTags(
    47  	ctx context.Context, logTagsToAdd CtxLogTags) context.Context {
    48  	currTags, _ := LogTagsFromContext(ctx)
    49  	newTags := make(CtxLogTags)
    50  	// Make a copy to avoid races
    51  	for key, tag := range currTags {
    52  		newTags[key] = tag
    53  	}
    54  	for key, tag := range logTagsToAdd {
    55  		newTags[key] = tag
    56  	}
    57  	return context.WithValue(ctx, CtxLogTagsKey, newTags)
    58  }
    59  
    60  // LogTagsFromContext returns the log tags being passed along with the
    61  // given context.
    62  func LogTagsFromContext(ctx context.Context) (CtxLogTags, bool) {
    63  	logTags, ok := ctx.Value(CtxLogTagsKey).(CtxLogTags)
    64  	return logTags, ok
    65  }
    66  
    67  // LogTagsFromContextRPC is a wrapper around LogTagsFromContext
    68  // that simply casts the result to the type expected by
    69  // rpc.Connection.
    70  func LogTagsFromContextRPC(ctx context.Context) (map[interface{}]string, bool) {
    71  	tags, ok := LogTagsFromContext(ctx)
    72  	return map[interface{}]string(tags), ok
    73  }
    74  
    75  type rpcTagKey string
    76  
    77  // ConvertRPCTagsToLogTags takes any RPC tags in the context and makes
    78  // them log tags.  It uses the string representation of the tag key,
    79  // rather than the original uniquely typed key, since the latter isn't
    80  // available in the RPC tags.
    81  func ConvertRPCTagsToLogTags(ctx context.Context) context.Context {
    82  	rpcTags, ok := rpc.RpcTagsFromContext(ctx)
    83  	if !ok {
    84  		return ctx
    85  	}
    86  
    87  	tags := make(CtxLogTags)
    88  	for key, value := range rpcTags {
    89  		// The map key should be a proper unique type, but that's not
    90  		// passed along in the RPC so just use our own string-like type.
    91  		tags[rpcTagKey(key)] = key
    92  		ctx = context.WithValue(ctx, rpcTagKey(key), value)
    93  	}
    94  	ctx = context.WithValue(ctx, rpc.CtxRpcTagsKey, nil)
    95  	return NewContextWithLogTags(ctx, tags)
    96  }
    97  
    98  type ExternalLogger interface {
    99  	Log(level keybase1.LogLevel, format string, args []interface{})
   100  }
   101  
   102  type Standard struct {
   103  	internal       *logging.Logger
   104  	filename       string
   105  	configureMutex sync.Mutex
   106  	module         string
   107  
   108  	externalHandler ExternalHandler
   109  }
   110  
   111  // Verify Standard fully implements the Logger interface.
   112  var _ Logger = (*Standard)(nil)
   113  
   114  // New creates a new Standard logger for module.
   115  func New(module string) *Standard {
   116  	return NewWithCallDepth(module, 0)
   117  }
   118  
   119  // NewWithCallDepth creates a new Standard logger for module, and when
   120  // printing file names and line numbers, it goes extraCallDepth up the
   121  // stack from where logger was invoked.
   122  func NewWithCallDepth(module string, extraCallDepth int) *Standard {
   123  	log := logging.MustGetLogger(module)
   124  	log.ExtraCalldepth = 1 + extraCallDepth
   125  
   126  	ret := &Standard{
   127  		internal: log,
   128  		module:   module,
   129  	}
   130  	ret.setLogLevelInfo()
   131  	return ret
   132  }
   133  
   134  func (log *Standard) setLogLevelInfo() {
   135  	initLoggingSetLevelMutex.Lock()
   136  	defer initLoggingSetLevelMutex.Unlock()
   137  
   138  	if _, found := initLoggingSetLevelCalled[log.module]; !found {
   139  		logging.SetLevel(logging.INFO, log.module)
   140  		initLoggingSetLevelCalled[log.module] = struct{}{}
   141  	}
   142  }
   143  
   144  func prepareString(
   145  	ctx context.Context, fmts string) string {
   146  	if ctx == nil {
   147  		return fmts
   148  	}
   149  	logTags, ok := LogTagsFromContext(ctx)
   150  	if !ok || len(logTags) == 0 {
   151  		return fmts
   152  	}
   153  	var tags []string
   154  	for key, tag := range logTags {
   155  		if v := ctx.Value(key); v != nil {
   156  			tags = append(tags, fmt.Sprintf("%s=%s", tag, v))
   157  		}
   158  	}
   159  	return fmts + " [tags:" + strings.Join(tags, ",") + "]"
   160  }
   161  
   162  func (log *Standard) Debug(fmt string, arg ...interface{}) {
   163  	log.internal.Debugf(fmt, arg...)
   164  	if log.externalHandler != nil {
   165  		log.externalHandler.Log(keybase1.LogLevel_DEBUG, fmt, arg)
   166  	}
   167  }
   168  
   169  func (log *Standard) CDebugf(ctx context.Context, fmt string,
   170  	arg ...interface{}) {
   171  	if log.internal.IsEnabledFor(logging.DEBUG) {
   172  		log.CloneWithAddedDepth(1).Debug(prepareString(ctx, fmt), arg...)
   173  	}
   174  }
   175  
   176  func (log *Standard) Info(fmt string, arg ...interface{}) {
   177  	log.internal.Infof(fmt, arg...)
   178  	if log.externalHandler != nil {
   179  		log.externalHandler.Log(keybase1.LogLevel_INFO, fmt, arg)
   180  	}
   181  }
   182  
   183  func (log *Standard) CInfof(ctx context.Context, fmt string,
   184  	arg ...interface{}) {
   185  	if log.internal.IsEnabledFor(logging.INFO) {
   186  		log.CloneWithAddedDepth(1).Info(prepareString(ctx, fmt), arg...)
   187  	}
   188  }
   189  
   190  func (log *Standard) Notice(fmt string, arg ...interface{}) {
   191  	log.internal.Noticef(fmt, arg...)
   192  	if log.externalHandler != nil {
   193  		log.externalHandler.Log(keybase1.LogLevel_NOTICE, fmt, arg)
   194  	}
   195  }
   196  
   197  func (log *Standard) CNoticef(ctx context.Context, fmt string,
   198  	arg ...interface{}) {
   199  	if log.internal.IsEnabledFor(logging.NOTICE) {
   200  		log.CloneWithAddedDepth(1).Notice(prepareString(ctx, fmt), arg...)
   201  	}
   202  }
   203  
   204  func (log *Standard) Warning(fmt string, arg ...interface{}) {
   205  	log.internal.Warningf(fmt, arg...)
   206  	if log.externalHandler != nil {
   207  		log.externalHandler.Log(keybase1.LogLevel_WARN, fmt, arg)
   208  	}
   209  }
   210  
   211  func (log *Standard) CWarningf(ctx context.Context, fmt string,
   212  	arg ...interface{}) {
   213  	if log.internal.IsEnabledFor(logging.WARNING) {
   214  		log.CloneWithAddedDepth(1).Warning(prepareString(ctx, fmt), arg...)
   215  	}
   216  }
   217  
   218  func (log *Standard) Error(fmt string, arg ...interface{}) {
   219  	log.internal.Errorf(fmt, arg...)
   220  	if log.externalHandler != nil {
   221  		log.externalHandler.Log(keybase1.LogLevel_ERROR, fmt, arg)
   222  	}
   223  }
   224  
   225  func (log *Standard) Errorf(fmt string, arg ...interface{}) {
   226  	log.CloneWithAddedDepth(1).Error(fmt, arg...)
   227  }
   228  
   229  func (log *Standard) CErrorf(ctx context.Context, fmt string,
   230  	arg ...interface{}) {
   231  	if log.internal.IsEnabledFor(logging.ERROR) {
   232  		log.CloneWithAddedDepth(1).Error(prepareString(ctx, fmt), arg...)
   233  	}
   234  }
   235  
   236  func (log *Standard) Critical(fmt string, arg ...interface{}) {
   237  	log.internal.Criticalf(fmt, arg...)
   238  	if log.externalHandler != nil {
   239  		log.externalHandler.Log(keybase1.LogLevel_CRITICAL, fmt, arg)
   240  	}
   241  }
   242  
   243  func (log *Standard) CCriticalf(ctx context.Context, fmt string,
   244  	arg ...interface{}) {
   245  	if log.internal.IsEnabledFor(logging.CRITICAL) {
   246  		log.CloneWithAddedDepth(1).Critical(prepareString(ctx, fmt), arg...)
   247  	}
   248  }
   249  
   250  func (log *Standard) Fatalf(fmt string, arg ...interface{}) {
   251  	log.internal.Fatalf(fmt, arg...)
   252  	if log.externalHandler != nil {
   253  		log.externalHandler.Log(keybase1.LogLevel_FATAL, fmt, arg)
   254  	}
   255  }
   256  
   257  func (log *Standard) CFatalf(ctx context.Context, fmt string,
   258  	arg ...interface{}) {
   259  	log.CloneWithAddedDepth(1).Fatalf(prepareString(ctx, fmt), arg...)
   260  }
   261  
   262  func (log *Standard) Profile(fmts string, arg ...interface{}) {
   263  	log.CloneWithAddedDepth(1).Debug(fmts, arg...)
   264  }
   265  
   266  // Configure sets the style of the log file, whether debugging (verbose)
   267  // is enabled and a filename. If a filename is provided here it will
   268  // be used for logging straight away (this is a new feature).
   269  // SetLogFileConfig provides a way to set the log file with more control on rotation.
   270  func (log *Standard) Configure(style string, debug bool, filename string) {
   271  	log.configureMutex.Lock()
   272  	defer log.configureMutex.Unlock()
   273  
   274  	log.filename = filename
   275  
   276  	var logfmt string
   277  
   278  	globalLock.Lock()
   279  	isTerm := stderrIsTerminal
   280  	globalLock.Unlock()
   281  
   282  	// TODO: how should setting the log file after a Configure be handled?
   283  	if isTerm {
   284  		if debug {
   285  			logfmt = fancyFormat
   286  		} else {
   287  			logfmt = defaultFormat
   288  		}
   289  	} else {
   290  		logfmt = fileFormat
   291  	}
   292  
   293  	// Override the format above if an explicit style was specified.
   294  	switch style {
   295  	case "default":
   296  		logfmt = defaultFormat // Default
   297  	case "plain":
   298  		logfmt = plainFormat // Plain
   299  	case "file":
   300  		logfmt = fileFormat // Good for logging to files
   301  	case "fancy":
   302  		logfmt = fancyFormat // Fancy, good for terminals with color
   303  	}
   304  
   305  	if debug {
   306  		logging.SetLevel(logging.DEBUG, log.module)
   307  	}
   308  
   309  	logging.SetFormatter(logging.MustStringFormatter(logfmt))
   310  
   311  }
   312  
   313  func OpenLogFile(filename string) (name string, file *os.File, err error) {
   314  	name = filename
   315  	if err = MakeParentDirs(name); err != nil {
   316  		return
   317  	}
   318  	file, err = os.OpenFile(name, (os.O_APPEND | os.O_WRONLY | os.O_CREATE), 0600)
   319  	if err != nil {
   320  		return
   321  	}
   322  	return
   323  }
   324  
   325  func FileExists(path string) (bool, error) {
   326  	_, err := os.Stat(path)
   327  	if err == nil {
   328  		return true, nil
   329  	}
   330  	if os.IsNotExist(err) {
   331  		return false, nil
   332  	}
   333  	return false, err
   334  }
   335  
   336  func MakeParentDirs(filename string) error {
   337  	dir, _ := filepath.Split(filename)
   338  	// If passed a plain file name as a path
   339  	if dir == "" {
   340  		return nil
   341  	}
   342  	exists, err := FileExists(dir)
   343  	if err != nil {
   344  		return err
   345  	}
   346  
   347  	if !exists {
   348  		err = os.MkdirAll(dir, permDir)
   349  		if err != nil {
   350  			return err
   351  		}
   352  	}
   353  	return nil
   354  }
   355  
   356  func (log *Standard) CloneWithAddedDepth(depth int) Logger {
   357  	clone := Standard{
   358  		filename:        log.filename,
   359  		module:          log.module,
   360  		externalHandler: log.externalHandler,
   361  	}
   362  	cloneInternal := *log.internal
   363  	clone.internal = &cloneInternal
   364  	clone.internal.ExtraCalldepth = log.internal.ExtraCalldepth + depth
   365  
   366  	return &clone
   367  }
   368  
   369  func (log *Standard) SetExternalHandler(handler ExternalHandler) {
   370  	log.externalHandler = handler
   371  }
   372  
   373  type UnforwardedLogger Standard
   374  
   375  func (log *Standard) GetUnforwardedLogger() *UnforwardedLogger {
   376  	return (*UnforwardedLogger)(log)
   377  }
   378  
   379  func (log *UnforwardedLogger) Debug(s string, args ...interface{}) {
   380  	log.internal.Debugf(s, args...)
   381  }
   382  
   383  func (log *UnforwardedLogger) Error(s string, args ...interface{}) {
   384  	log.internal.Errorf(s, args...)
   385  }
   386  
   387  func (log *UnforwardedLogger) Errorf(s string, args ...interface{}) {
   388  	log.internal.Errorf(s, args...)
   389  }
   390  
   391  func (log *UnforwardedLogger) Warning(s string, args ...interface{}) {
   392  	log.internal.Warningf(s, args...)
   393  }
   394  
   395  func (log *UnforwardedLogger) Info(s string, args ...interface{}) {
   396  	log.internal.Infof(s, args...)
   397  }
   398  
   399  func (log *UnforwardedLogger) Profile(s string, args ...interface{}) {
   400  	log.internal.Debugf(s, args...)
   401  }