github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/logging/logging.go (about)

     1  package logging
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  	"syscall"
    10  
    11  	// go.etcd.io/etcd imports capnslog, which calls log.SetOutput in its
    12  	// init() function, so importing it here means that our log.SetOutput
    13  	// wins. this is fixed in coreos v3.5, which is not released yet. See
    14  	// https://github.com/etcd-io/etcd/issues/12498 for more information.
    15  	_ "github.com/coreos/pkg/capnslog"
    16  	"github.com/hashicorp/go-hclog"
    17  )
    18  
    19  // These are the environmental variables that determine if we log, and if
    20  // we log whether or not the log should go to a file.
    21  const (
    22  	envLog     = "TF_LOG"
    23  	envLogFile = "TF_LOG_PATH"
    24  
    25  	// Allow logging of specific subsystems.
    26  	// We only separate core and providers for now, but this could be extended
    27  	// to other loggers, like provisioners and remote-state backends.
    28  	envLogCore     = "TF_LOG_CORE"
    29  	envLogProvider = "TF_LOG_PROVIDER"
    30  )
    31  
    32  var (
    33  	// ValidLevels are the log level names that Durgaform recognizes.
    34  	ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
    35  
    36  	// logger is the global hclog logger
    37  	logger hclog.Logger
    38  
    39  	// logWriter is a global writer for logs, to be used with the std log package
    40  	logWriter io.Writer
    41  
    42  	// initialize our cache of panic output from providers
    43  	panics = &panicRecorder{
    44  		panics:   make(map[string][]string),
    45  		maxLines: 100,
    46  	}
    47  )
    48  
    49  func init() {
    50  	logger = newHCLogger("")
    51  	logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})
    52  
    53  	// set up the default std library logger to use our output
    54  	log.SetFlags(0)
    55  	log.SetPrefix("")
    56  	log.SetOutput(logWriter)
    57  }
    58  
    59  // SetupTempLog adds a new log sink which writes all logs to the given file.
    60  func RegisterSink(f *os.File) {
    61  	l, ok := logger.(hclog.InterceptLogger)
    62  	if !ok {
    63  		panic("global logger is not an InterceptLogger")
    64  	}
    65  
    66  	if f == nil {
    67  		return
    68  	}
    69  
    70  	l.RegisterSink(hclog.NewSinkAdapter(&hclog.LoggerOptions{
    71  		Level:  hclog.Trace,
    72  		Output: f,
    73  	}))
    74  }
    75  
    76  // LogOutput return the default global log io.Writer
    77  func LogOutput() io.Writer {
    78  	return logWriter
    79  }
    80  
    81  // HCLogger returns the default global hclog logger
    82  func HCLogger() hclog.Logger {
    83  	return logger
    84  }
    85  
    86  // newHCLogger returns a new hclog.Logger instance with the given name
    87  func newHCLogger(name string) hclog.Logger {
    88  	logOutput := io.Writer(os.Stderr)
    89  	logLevel, json := globalLogLevel()
    90  
    91  	if logPath := os.Getenv(envLogFile); logPath != "" {
    92  		f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666)
    93  		if err != nil {
    94  			fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err)
    95  		} else {
    96  			logOutput = f
    97  		}
    98  	}
    99  
   100  	return hclog.NewInterceptLogger(&hclog.LoggerOptions{
   101  		Name:              name,
   102  		Level:             logLevel,
   103  		Output:            logOutput,
   104  		IndependentLevels: true,
   105  		JSONFormat:        json,
   106  	})
   107  }
   108  
   109  // NewLogger returns a new logger based in the current global logger, with the
   110  // given name appended.
   111  func NewLogger(name string) hclog.Logger {
   112  	if name == "" {
   113  		panic("logger name required")
   114  	}
   115  	return &logPanicWrapper{
   116  		Logger: logger.Named(name),
   117  	}
   118  }
   119  
   120  // NewProviderLogger returns a logger for the provider plugin, possibly with a
   121  // different log level from the global logger.
   122  func NewProviderLogger(prefix string) hclog.Logger {
   123  	l := &logPanicWrapper{
   124  		Logger: logger.Named(prefix + "provider"),
   125  	}
   126  
   127  	level := providerLogLevel()
   128  	logger.Debug("created provider logger", "level", level)
   129  
   130  	l.SetLevel(level)
   131  	return l
   132  }
   133  
   134  // CurrentLogLevel returns the current log level string based the environment vars
   135  func CurrentLogLevel() string {
   136  	ll, _ := globalLogLevel()
   137  	return strings.ToUpper(ll.String())
   138  }
   139  
   140  func providerLogLevel() hclog.Level {
   141  	providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider))
   142  	if providerEnvLevel == "" {
   143  		providerEnvLevel = strings.ToUpper(os.Getenv(envLog))
   144  	}
   145  
   146  	return parseLogLevel(providerEnvLevel)
   147  }
   148  
   149  func globalLogLevel() (hclog.Level, bool) {
   150  	var json bool
   151  	envLevel := strings.ToUpper(os.Getenv(envLog))
   152  	if envLevel == "" {
   153  		envLevel = strings.ToUpper(os.Getenv(envLogCore))
   154  	}
   155  	if envLevel == "JSON" {
   156  		json = true
   157  	}
   158  	return parseLogLevel(envLevel), json
   159  }
   160  
   161  func parseLogLevel(envLevel string) hclog.Level {
   162  	if envLevel == "" {
   163  		return hclog.Off
   164  	}
   165  	if envLevel == "JSON" {
   166  		envLevel = "TRACE"
   167  	}
   168  
   169  	logLevel := hclog.Trace
   170  	if isValidLogLevel(envLevel) {
   171  		logLevel = hclog.LevelFromString(envLevel)
   172  	} else {
   173  		fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v",
   174  			envLevel, ValidLevels)
   175  	}
   176  
   177  	return logLevel
   178  }
   179  
   180  // IsDebugOrHigher returns whether or not the current log level is debug or trace
   181  func IsDebugOrHigher() bool {
   182  	level, _ := globalLogLevel()
   183  	return level == hclog.Debug || level == hclog.Trace
   184  }
   185  
   186  func isValidLogLevel(level string) bool {
   187  	for _, l := range ValidLevels {
   188  		if level == string(l) {
   189  			return true
   190  		}
   191  	}
   192  
   193  	return false
   194  }
   195  
   196  // PluginOutputMonitor creates an io.Writer that will warn about any writes in
   197  // the default logger. This is used to catch unexpected output from plugins,
   198  // notifying them about the problem as well as surfacing the lost data for
   199  // context.
   200  func PluginOutputMonitor(source string) io.Writer {
   201  	return pluginOutputMonitor{
   202  		source: source,
   203  		log:    logger,
   204  	}
   205  }
   206  
   207  // pluginOutputMonitor is an io.Writer that logs all writes as
   208  // "unexpected data" with the source name.
   209  type pluginOutputMonitor struct {
   210  	source string
   211  	log    hclog.Logger
   212  }
   213  
   214  func (w pluginOutputMonitor) Write(d []byte) (int, error) {
   215  	// Limit the write size to 1024 bytes We're not expecting any data to come
   216  	// through this channel, so accidental writes will usually be stray fmt
   217  	// debugging statements and the like, but we want to provide context to the
   218  	// provider to indicate what the unexpected data might be.
   219  	n := len(d)
   220  	if n > 1024 {
   221  		d = append(d[:1024], '.', '.', '.')
   222  	}
   223  
   224  	w.log.Warn("unexpected data", w.source, strings.TrimSpace(string(d)))
   225  	return n, nil
   226  }