github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/internal/logger/logger.go (about)

     1  // Copyright (C) MongoDB, Inc. 2023-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package logger
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  // DefaultMaxDocumentLength is the default maximum number of bytes that can be
    17  // logged for a stringified BSON document.
    18  const DefaultMaxDocumentLength = 1000
    19  
    20  // TruncationSuffix are trailling ellipsis "..." appended to a message to
    21  // indicate to the user that truncation occurred. This constant does not count
    22  // toward the max document length.
    23  const TruncationSuffix = "..."
    24  
    25  const logSinkPathEnvVar = "MONGODB_LOG_PATH"
    26  const maxDocumentLengthEnvVar = "MONGODB_LOG_MAX_DOCUMENT_LENGTH"
    27  
    28  // LogSink represents a logging implementation, this interface should be 1-1
    29  // with the exported "LogSink" interface in the mongo/options package.
    30  type LogSink interface {
    31  	// Info logs a non-error message with the given key/value pairs. The
    32  	// level argument is provided for optional logging.
    33  	Info(level int, msg string, keysAndValues ...interface{})
    34  
    35  	// Error logs an error, with the given message and key/value pairs.
    36  	Error(err error, msg string, keysAndValues ...interface{})
    37  }
    38  
    39  // Logger represents the configuration for the internal logger.
    40  type Logger struct {
    41  	ComponentLevels   map[Component]Level // Log levels for each component.
    42  	Sink              LogSink             // LogSink for log printing.
    43  	MaxDocumentLength uint                // Command truncation width.
    44  	logFile           *os.File            // File to write logs to.
    45  }
    46  
    47  // New will construct a new logger. If any of the given options are the
    48  // zero-value of the argument type, then the constructor will attempt to
    49  // source the data from the environment. If the environment has not been set,
    50  // then the constructor will the respective default values.
    51  func New(sink LogSink, maxDocLen uint, compLevels map[Component]Level) (*Logger, error) {
    52  	logger := &Logger{
    53  		ComponentLevels:   selectComponentLevels(compLevels),
    54  		MaxDocumentLength: selectMaxDocumentLength(maxDocLen),
    55  	}
    56  
    57  	sink, logFile, err := selectLogSink(sink)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	logger.Sink = sink
    63  	logger.logFile = logFile
    64  
    65  	return logger, nil
    66  }
    67  
    68  // Close will close the logger's log file, if it exists.
    69  func (logger *Logger) Close() error {
    70  	if logger.logFile != nil {
    71  		return logger.logFile.Close()
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // LevelComponentEnabled will return true if the given LogLevel is enabled for
    78  // the given LogComponent. If the ComponentLevels on the logger are enabled for
    79  // "ComponentAll", then this function will return true for any level bound by
    80  // the level assigned to "ComponentAll".
    81  //
    82  // If the level is not enabled (i.e. LevelOff), then false is returned. This is
    83  // to avoid false positives, such as returning "true" for a component that is
    84  // not enabled. For example, without this condition, an empty LevelComponent
    85  // would be considered "enabled" for "LevelOff".
    86  func (logger *Logger) LevelComponentEnabled(level Level, component Component) bool {
    87  	if level == LevelOff {
    88  		return false
    89  	}
    90  
    91  	if logger.ComponentLevels == nil {
    92  		return false
    93  	}
    94  
    95  	return logger.ComponentLevels[component] >= level ||
    96  		logger.ComponentLevels[ComponentAll] >= level
    97  }
    98  
    99  // Print will synchronously print the given message to the configured LogSink.
   100  // If the LogSink is nil, then this method will do nothing. Future work could be done to make
   101  // this method asynchronous, see buffer management in libraries such as log4j.
   102  func (logger *Logger) Print(level Level, component Component, msg string, keysAndValues ...interface{}) {
   103  	// If the level is not enabled for the component, then
   104  	// skip the message.
   105  	if !logger.LevelComponentEnabled(level, component) {
   106  		return
   107  	}
   108  
   109  	// If the sink is nil, then skip the message.
   110  	if logger.Sink == nil {
   111  		return
   112  	}
   113  
   114  	logger.Sink.Info(int(level)-DiffToInfo, msg, keysAndValues...)
   115  }
   116  
   117  // Error logs an error, with the given message and key/value pairs.
   118  // It functions similarly to Print, but may have unique behavior, and should be
   119  // preferred for logging errors.
   120  func (logger *Logger) Error(err error, msg string, keysAndValues ...interface{}) {
   121  	if logger.Sink == nil {
   122  		return
   123  	}
   124  
   125  	logger.Sink.Error(err, msg, keysAndValues...)
   126  }
   127  
   128  // selectMaxDocumentLength will return the integer value of the first non-zero
   129  // function, with the user-defined function taking priority over the environment
   130  // variables. For the environment, the function will attempt to get the value of
   131  // "MONGODB_LOG_MAX_DOCUMENT_LENGTH" and parse it as an unsigned integer. If the
   132  // environment variable is not set or is not an unsigned integer, then this
   133  // function will return the default max document length.
   134  func selectMaxDocumentLength(maxDocLen uint) uint {
   135  	if maxDocLen != 0 {
   136  		return maxDocLen
   137  	}
   138  
   139  	maxDocLenEnv := os.Getenv(maxDocumentLengthEnvVar)
   140  	if maxDocLenEnv != "" {
   141  		maxDocLenEnvInt, err := strconv.ParseUint(maxDocLenEnv, 10, 32)
   142  		if err == nil {
   143  			return uint(maxDocLenEnvInt)
   144  		}
   145  	}
   146  
   147  	return DefaultMaxDocumentLength
   148  }
   149  
   150  const (
   151  	logSinkPathStdout = "stdout"
   152  	logSinkPathStderr = "stderr"
   153  )
   154  
   155  // selectLogSink will return the first non-nil LogSink, with the user-defined
   156  // LogSink taking precedence over the environment-defined LogSink. If no LogSink
   157  // is defined, then this function will return a LogSink that writes to stderr.
   158  func selectLogSink(sink LogSink) (LogSink, *os.File, error) {
   159  	if sink != nil {
   160  		return sink, nil, nil
   161  	}
   162  
   163  	path := os.Getenv(logSinkPathEnvVar)
   164  	lowerPath := strings.ToLower(path)
   165  
   166  	if lowerPath == string(logSinkPathStderr) {
   167  		return NewIOSink(os.Stderr), nil, nil
   168  	}
   169  
   170  	if lowerPath == string(logSinkPathStdout) {
   171  		return NewIOSink(os.Stdout), nil, nil
   172  	}
   173  
   174  	if path != "" {
   175  		logFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
   176  		if err != nil {
   177  			return nil, nil, fmt.Errorf("unable to open log file: %v", err)
   178  		}
   179  
   180  		return NewIOSink(logFile), logFile, nil
   181  	}
   182  
   183  	return NewIOSink(os.Stderr), nil, nil
   184  }
   185  
   186  // selectComponentLevels returns a new map of LogComponents to LogLevels that is
   187  // the result of merging the user-defined data with the environment, with the
   188  // user-defined data taking priority.
   189  func selectComponentLevels(componentLevels map[Component]Level) map[Component]Level {
   190  	selected := make(map[Component]Level)
   191  
   192  	// Determine if the "MONGODB_LOG_ALL" environment variable is set.
   193  	var globalEnvLevel *Level
   194  	if all := os.Getenv(mongoDBLogAllEnvVar); all != "" {
   195  		level := ParseLevel(all)
   196  		globalEnvLevel = &level
   197  	}
   198  
   199  	for envVar, component := range componentEnvVarMap {
   200  		// If the component already has a level, then skip it.
   201  		if _, ok := componentLevels[component]; ok {
   202  			selected[component] = componentLevels[component]
   203  
   204  			continue
   205  		}
   206  
   207  		// If the "MONGODB_LOG_ALL" environment variable is set, then
   208  		// set the level for the component to the value of the
   209  		// environment variable.
   210  		if globalEnvLevel != nil {
   211  			selected[component] = *globalEnvLevel
   212  
   213  			continue
   214  		}
   215  
   216  		// Otherwise, set the level for the component to the value of
   217  		// the environment variable.
   218  		selected[component] = ParseLevel(os.Getenv(envVar))
   219  	}
   220  
   221  	return selected
   222  }
   223  
   224  // truncate will truncate a string to the given width, appending "..." to the
   225  // end of the string if it is truncated. This routine is safe for multi-byte
   226  // characters.
   227  func truncate(str string, width uint) string {
   228  	if width == 0 {
   229  		return ""
   230  	}
   231  
   232  	if len(str) <= int(width) {
   233  		return str
   234  	}
   235  
   236  	// Truncate the byte slice of the string to the given width.
   237  	newStr := str[:width]
   238  
   239  	// Check if the last byte is at the beginning of a multi-byte character.
   240  	// If it is, then remove the last byte.
   241  	if newStr[len(newStr)-1]&0xC0 == 0xC0 {
   242  		return newStr[:len(newStr)-1] + TruncationSuffix
   243  	}
   244  
   245  	// Check if the last byte is in the middle of a multi-byte character. If
   246  	// it is, then step back until we find the beginning of the character.
   247  	if newStr[len(newStr)-1]&0xC0 == 0x80 {
   248  		for i := len(newStr) - 1; i >= 0; i-- {
   249  			if newStr[i]&0xC0 == 0xC0 {
   250  				return newStr[:i] + TruncationSuffix
   251  			}
   252  		}
   253  	}
   254  
   255  	return newStr + TruncationSuffix
   256  }
   257  
   258  // FormatMessage formats a BSON document for logging. The document is truncated
   259  // to the given width.
   260  func FormatMessage(msg string, width uint) string {
   261  	if len(msg) == 0 {
   262  		return "{}"
   263  	}
   264  
   265  	return truncate(msg, width)
   266  }