log.go raw

   1  // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates.  All rights reserved.
   2  // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
   3  
   4  package common
   5  
   6  import (
   7  	"fmt"
   8  	"io"
   9  	"io/ioutil"
  10  	"log"
  11  	"os"
  12  	"strings"
  13  	"sync"
  14  	"time"
  15  )
  16  
  17  // sdkLogger an interface for logging in the SDK
  18  type sdkLogger interface {
  19  	//LogLevel returns the log level of sdkLogger
  20  	LogLevel() int
  21  
  22  	//Log logs v with the provided format if the current log level is loglevel
  23  	Log(logLevel int, format string, v ...interface{}) error
  24  }
  25  
  26  // noLogging no logging messages
  27  const noLogging = 0
  28  
  29  // infoLogging minimal logging messages
  30  const infoLogging = 1
  31  
  32  // debugLogging some logging messages
  33  const debugLogging = 2
  34  
  35  // verboseLogging all logging messages
  36  const verboseLogging = 3
  37  
  38  // DefaultSDKLogger the default implementation of the sdkLogger
  39  type DefaultSDKLogger struct {
  40  	currentLoggingLevel int
  41  	verboseLogger       *log.Logger
  42  	debugLogger         *log.Logger
  43  	infoLogger          *log.Logger
  44  	nullLogger          *log.Logger
  45  }
  46  
  47  // defaultLogger is the defaultLogger in the SDK
  48  var defaultLogger sdkLogger
  49  var loggerLock sync.Mutex
  50  var file *os.File
  51  
  52  // initializes the SDK defaultLogger as a defaultLogger
  53  func init() {
  54  	l, _ := NewSDKLogger()
  55  	SetSDKLogger(l)
  56  }
  57  
  58  // SetSDKLogger sets the logger used by the sdk
  59  func SetSDKLogger(logger sdkLogger) {
  60  	loggerLock.Lock()
  61  	defaultLogger = logger
  62  	loggerLock.Unlock()
  63  }
  64  
  65  // NewSDKLogger creates a defaultSDKLogger
  66  // Debug logging is turned on/off by the presence of the environment variable "OCI_GO_SDK_DEBUG"
  67  // The value of the "OCI_GO_SDK_DEBUG" environment variable controls the logging level.
  68  // "null" outputs no log messages
  69  // "i" or "info" outputs minimal log messages
  70  // "d" or "debug" outputs some logs messages
  71  // "v" or "verbose" outputs all logs messages, including body of requests
  72  func NewSDKLogger() (DefaultSDKLogger, error) {
  73  	logger := DefaultSDKLogger{}
  74  
  75  	logger.currentLoggingLevel = noLogging
  76  	logger.verboseLogger = log.New(os.Stderr, "VERBOSE ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  77  	logger.debugLogger = log.New(os.Stderr, "DEBUG ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  78  	logger.infoLogger = log.New(os.Stderr, "INFO ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  79  	logger.nullLogger = log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  80  
  81  	configured, isLogEnabled := os.LookupEnv("OCI_GO_SDK_DEBUG")
  82  
  83  	// If env variable not present turn logging off
  84  	if !isLogEnabled {
  85  		logger.currentLoggingLevel = noLogging
  86  	} else {
  87  		logOutputModeConfig(logger)
  88  
  89  		switch strings.ToLower(configured) {
  90  		case "null":
  91  			logger.currentLoggingLevel = noLogging
  92  			break
  93  		case "i", "info":
  94  			logger.currentLoggingLevel = infoLogging
  95  			break
  96  		case "d", "debug":
  97  			logger.currentLoggingLevel = debugLogging
  98  			break
  99  		//1 here for backwards compatibility
 100  		case "v", "verbose", "1":
 101  			logger.currentLoggingLevel = verboseLogging
 102  			break
 103  		default:
 104  			logger.currentLoggingLevel = infoLogging
 105  		}
 106  		logger.infoLogger.Println("logger level set to: ", logger.currentLoggingLevel)
 107  	}
 108  
 109  	return logger, nil
 110  }
 111  
 112  func (l DefaultSDKLogger) getLoggerForLevel(logLevel int) *log.Logger {
 113  	if logLevel > l.currentLoggingLevel {
 114  		return l.nullLogger
 115  	}
 116  
 117  	switch logLevel {
 118  	case noLogging:
 119  		return l.nullLogger
 120  	case infoLogging:
 121  		return l.infoLogger
 122  	case debugLogging:
 123  		return l.debugLogger
 124  	case verboseLogging:
 125  		return l.verboseLogger
 126  	default:
 127  		return l.nullLogger
 128  	}
 129  }
 130  
 131  // Set SDK Log output mode
 132  // Output mode is switched based on environment variable "OCI_GO_SDK_LOG_OUPUT_MODE"
 133  // "file" outputs log to a specific file
 134  // "combine" outputs log to both stderr and specific file
 135  // other unsupported value outputs log to stderr
 136  // output file can be set via environment variable "OCI_GO_SDK_LOG_FILE"
 137  // if this environment variable is not set, a default log file will be created under project root path
 138  func logOutputModeConfig(logger DefaultSDKLogger) {
 139  	logMode, isLogOutputModeEnabled := os.LookupEnv("OCI_GO_SDK_LOG_OUTPUT_MODE")
 140  	if !isLogOutputModeEnabled {
 141  		return
 142  	}
 143  	fileName, isLogFileNameProvided := os.LookupEnv("OCI_GO_SDK_LOG_FILE")
 144  	if !isLogFileNameProvided {
 145  		fileName = fmt.Sprintf("logging_%v%s", time.Now().Unix(), ".log")
 146  	}
 147  
 148  	switch strings.ToLower(logMode) {
 149  	case "file", "f":
 150  		file = openLogOutputFile(logger, fileName)
 151  		logger.infoLogger.SetOutput(file)
 152  		logger.debugLogger.SetOutput(file)
 153  		logger.verboseLogger.SetOutput(file)
 154  		break
 155  	case "combine", "c":
 156  		file = openLogOutputFile(logger, fileName)
 157  		wrt := io.MultiWriter(os.Stderr, file)
 158  
 159  		logger.infoLogger.SetOutput(wrt)
 160  		logger.debugLogger.SetOutput(wrt)
 161  		logger.verboseLogger.SetOutput(wrt)
 162  		break
 163  	}
 164  }
 165  
 166  func openLogOutputFile(logger DefaultSDKLogger, fileName string) *os.File {
 167  	file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
 168  	if err != nil {
 169  		logger.verboseLogger.Fatal(err)
 170  	}
 171  	return file
 172  }
 173  
 174  // CloseLogFile close the logging file and return error
 175  func CloseLogFile() error {
 176  	return file.Close()
 177  }
 178  
 179  // LogLevel returns the current debug level
 180  func (l DefaultSDKLogger) LogLevel() int {
 181  	return l.currentLoggingLevel
 182  }
 183  
 184  // Log logs v with the provided format if the current log level is loglevel
 185  func (l DefaultSDKLogger) Log(logLevel int, format string, v ...interface{}) error {
 186  	logger := l.getLoggerForLevel(logLevel)
 187  	logger.Output(4, fmt.Sprintf(format, v...))
 188  	return nil
 189  }
 190  
 191  // Logln logs v appending a new line at the end
 192  // Deprecated
 193  func Logln(v ...interface{}) {
 194  	defaultLogger.Log(infoLogging, "%v\n", v...)
 195  }
 196  
 197  // Logf logs v with the provided format
 198  func Logf(format string, v ...interface{}) {
 199  	defaultLogger.Log(infoLogging, format, v...)
 200  }
 201  
 202  // Debugf logs v with the provided format if debug mode is set
 203  func Debugf(format string, v ...interface{}) {
 204  	defaultLogger.Log(debugLogging, format, v...)
 205  }
 206  
 207  // Debug  logs v if debug mode is set
 208  func Debug(v ...interface{}) {
 209  	m := fmt.Sprint(v...)
 210  	defaultLogger.Log(debugLogging, "%s", m)
 211  }
 212  
 213  // Debugln logs v appending a new line if debug mode is set
 214  func Debugln(v ...interface{}) {
 215  	m := fmt.Sprint(v...)
 216  	defaultLogger.Log(debugLogging, "%s\n", m)
 217  }
 218  
 219  // IfDebug executes closure if debug is enabled
 220  func IfDebug(fn func()) {
 221  	if defaultLogger.LogLevel() >= debugLogging {
 222  		fn()
 223  	}
 224  }
 225  
 226  // IfInfo executes closure if info is enabled
 227  func IfInfo(fn func()) {
 228  	if defaultLogger.LogLevel() >= infoLogging {
 229  		fn()
 230  	}
 231  }
 232