github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/openstack/obs/log.go (about)

     1  // Copyright 2019 Huawei Technologies Co.,Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
     3  // this file except in compliance with the License.  You may obtain a copy of the
     4  // License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed
     9  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    10  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    11  // specific language governing permissions and limitations under the License.
    12  
    13  package obs
    14  
    15  import (
    16  	"fmt"
    17  	"log"
    18  	"net/http"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strings"
    23  	"sync"
    24  )
    25  
    26  // Level defines the level of the log
    27  type Level int
    28  
    29  const (
    30  	LEVEL_OFF   Level = 500
    31  	LEVEL_ERROR Level = 400
    32  	LEVEL_WARN  Level = 300
    33  	LEVEL_INFO  Level = 200
    34  	LEVEL_DEBUG Level = 100
    35  )
    36  
    37  var logLevelMap = map[Level]string{
    38  	LEVEL_OFF:   "[OFF]: ",
    39  	LEVEL_ERROR: "[ERROR]: ",
    40  	LEVEL_WARN:  "[WARN]: ",
    41  	LEVEL_INFO:  "[INFO]: ",
    42  	LEVEL_DEBUG: "[DEBUG]: ",
    43  }
    44  
    45  type logConfType struct {
    46  	level        Level
    47  	logToConsole bool
    48  	logFullPath  string
    49  	maxLogSize   int64
    50  	backups      int
    51  }
    52  
    53  func getDefaultLogConf() logConfType {
    54  	return logConfType{
    55  		level:        LEVEL_WARN,
    56  		logToConsole: false,
    57  		logFullPath:  "",
    58  		maxLogSize:   1024 * 1024 * 30, //30MB
    59  		backups:      10,
    60  	}
    61  }
    62  
    63  var logConf logConfType
    64  
    65  type loggerWrapper struct {
    66  	fullPath        string
    67  	fd              *os.File
    68  	ch              chan string
    69  	wg              sync.WaitGroup
    70  	queue           []string
    71  	logger          *log.Logger
    72  	index           int
    73  	cacheCount      int
    74  	closed          bool
    75  	formatLoggerNow func(string) string
    76  }
    77  
    78  func (lw *loggerWrapper) doInit() {
    79  	lw.queue = make([]string, 0, lw.cacheCount)
    80  	lw.logger = log.New(lw.fd, "", 0)
    81  	lw.ch = make(chan string, lw.cacheCount)
    82  	if lw.formatLoggerNow == nil {
    83  		lw.formatLoggerNow = FormatUtcNow
    84  	}
    85  	lw.wg.Add(1)
    86  	go lw.doWrite()
    87  }
    88  
    89  func (lw *loggerWrapper) rotate() {
    90  	stat, err := lw.fd.Stat()
    91  	if err != nil {
    92  		_err := lw.fd.Close()
    93  		if _err != nil {
    94  			doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
    95  		}
    96  		panic(err)
    97  	}
    98  	if stat.Size() >= logConf.maxLogSize {
    99  		_err := lw.fd.Sync()
   100  		if _err != nil {
   101  			panic(err)
   102  		}
   103  		_err = lw.fd.Close()
   104  		if _err != nil {
   105  			doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   106  		}
   107  		if lw.index > logConf.backups {
   108  			lw.index = 1
   109  		}
   110  		_err = os.Rename(lw.fullPath, lw.fullPath+"."+IntToString(lw.index))
   111  		if _err != nil {
   112  			panic(err)
   113  		}
   114  		lw.index++
   115  
   116  		fd, err := os.OpenFile(lw.fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
   117  		if err != nil {
   118  			panic(err)
   119  		}
   120  		lw.fd = fd
   121  		lw.logger.SetOutput(lw.fd)
   122  	}
   123  }
   124  
   125  func (lw *loggerWrapper) doFlush() {
   126  	lw.rotate()
   127  	for _, m := range lw.queue {
   128  		lw.logger.Println(m)
   129  	}
   130  	err := lw.fd.Sync()
   131  	if err != nil {
   132  		panic(err)
   133  	}
   134  }
   135  
   136  func (lw *loggerWrapper) doClose() {
   137  	lw.closed = true
   138  	close(lw.ch)
   139  	lw.wg.Wait()
   140  }
   141  
   142  func (lw *loggerWrapper) doWrite() {
   143  	defer lw.wg.Done()
   144  	for {
   145  		msg, ok := <-lw.ch
   146  		if !ok {
   147  			lw.doFlush()
   148  			_err := lw.fd.Close()
   149  			if _err != nil {
   150  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   151  			}
   152  			break
   153  		}
   154  		if len(lw.queue) >= lw.cacheCount {
   155  			lw.doFlush()
   156  			lw.queue = make([]string, 0, lw.cacheCount)
   157  		}
   158  		lw.queue = append(lw.queue, msg)
   159  	}
   160  
   161  }
   162  
   163  func (lw *loggerWrapper) Printf(format string, v ...interface{}) {
   164  	if !lw.closed {
   165  		msg := fmt.Sprintf(format, v...)
   166  		lw.ch <- msg
   167  	}
   168  }
   169  
   170  var consoleLogger *log.Logger
   171  var fileLogger *loggerWrapper
   172  var lock = new(sync.RWMutex)
   173  
   174  func isDebugLogEnabled() bool {
   175  	return logConf.level <= LEVEL_DEBUG
   176  }
   177  
   178  func isErrorLogEnabled() bool {
   179  	return logConf.level <= LEVEL_ERROR
   180  }
   181  
   182  func isWarnLogEnabled() bool {
   183  	return logConf.level <= LEVEL_WARN
   184  }
   185  
   186  func isInfoLogEnabled() bool {
   187  	return logConf.level <= LEVEL_INFO
   188  }
   189  
   190  func reset() {
   191  	if fileLogger != nil {
   192  		fileLogger.doClose()
   193  		fileLogger = nil
   194  	}
   195  	consoleLogger = nil
   196  	logConf = getDefaultLogConf()
   197  }
   198  
   199  type logConfig func(lw *loggerWrapper)
   200  
   201  func WithFormatLoggerTime(formatNow func(string) string) logConfig {
   202  	return func(lw *loggerWrapper) {
   203  		lw.formatLoggerNow = formatNow
   204  	}
   205  }
   206  
   207  // InitLog enable logging function with default cacheCnt
   208  func InitLog(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, logConfigs ...logConfig) error {
   209  
   210  	return InitLogWithCacheCnt(logFullPath, maxLogSize, backups, level, logToConsole, 50, logConfigs...)
   211  }
   212  
   213  // InitLogWithCacheCnt enable logging function
   214  func InitLogWithCacheCnt(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, cacheCnt int, logConfigs ...logConfig) error {
   215  	lock.Lock()
   216  	defer lock.Unlock()
   217  	if cacheCnt <= 0 {
   218  		cacheCnt = 50
   219  	}
   220  	reset()
   221  	if fullPath := strings.TrimSpace(logFullPath); fullPath != "" {
   222  		_fullPath, err := filepath.Abs(fullPath)
   223  		if err != nil {
   224  			return err
   225  		}
   226  
   227  		if !strings.HasSuffix(_fullPath, ".log") {
   228  			_fullPath += ".log"
   229  		}
   230  
   231  		stat, fd, err := initLogFile(_fullPath)
   232  		if err != nil {
   233  			return err
   234  		}
   235  
   236  		prefix := stat.Name() + "."
   237  		index := 1
   238  		var timeIndex int64 = 0
   239  		walkFunc := func(path string, info os.FileInfo, err error) error {
   240  			if err == nil {
   241  				if name := info.Name(); strings.HasPrefix(name, prefix) {
   242  					if i := StringToInt(name[len(prefix):], 0); i >= index && info.ModTime().Unix() >= timeIndex {
   243  						timeIndex = info.ModTime().Unix()
   244  						index = i + 1
   245  					}
   246  				}
   247  			}
   248  			return err
   249  		}
   250  
   251  		if err = filepath.Walk(filepath.Dir(_fullPath), walkFunc); err != nil {
   252  			_err := fd.Close()
   253  			if _err != nil {
   254  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   255  			}
   256  			return err
   257  		}
   258  
   259  		fileLogger = &loggerWrapper{fullPath: _fullPath, fd: fd, index: index, cacheCount: cacheCnt, closed: false}
   260  		for _, logConfig := range logConfigs {
   261  			logConfig(fileLogger)
   262  		}
   263  		fileLogger.doInit()
   264  	}
   265  	if maxLogSize > 0 {
   266  		logConf.maxLogSize = maxLogSize
   267  	}
   268  	if backups > 0 {
   269  		logConf.backups = backups
   270  	}
   271  	logConf.level = level
   272  	if logToConsole {
   273  		consoleLogger = log.New(os.Stdout, "", log.LstdFlags)
   274  	}
   275  	return nil
   276  }
   277  
   278  func initLogFile(_fullPath string) (os.FileInfo, *os.File, error) {
   279  	stat, err := os.Stat(_fullPath)
   280  	if err == nil && stat.IsDir() {
   281  		return nil, nil, fmt.Errorf("logFullPath:[%s] is a directory", _fullPath)
   282  	} else if err = os.MkdirAll(filepath.Dir(_fullPath), os.ModePerm); err != nil {
   283  		return nil, nil, err
   284  	}
   285  
   286  	fd, err := os.OpenFile(_fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
   287  	if err != nil {
   288  		_err := fd.Close()
   289  		if _err != nil {
   290  			doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   291  		}
   292  		return nil, nil, err
   293  	}
   294  
   295  	if stat == nil {
   296  		stat, err = os.Stat(_fullPath)
   297  		if err != nil {
   298  			_err := fd.Close()
   299  			if _err != nil {
   300  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   301  			}
   302  			return nil, nil, err
   303  		}
   304  	}
   305  
   306  	return stat, fd, nil
   307  }
   308  
   309  // CloseLog disable logging and synchronize cache data to log files
   310  func CloseLog() {
   311  	if logEnabled() {
   312  		lock.Lock()
   313  		defer lock.Unlock()
   314  		reset()
   315  	}
   316  }
   317  
   318  func logEnabled() bool {
   319  	return consoleLogger != nil || fileLogger != nil
   320  }
   321  
   322  // DoLog writes log messages to the logger
   323  func DoLog(level Level, format string, v ...interface{}) {
   324  	doLog(level, format, v...)
   325  }
   326  
   327  func doLog(level Level, format string, v ...interface{}) {
   328  	if logEnabled() && logConf.level <= level {
   329  		msg := fmt.Sprintf(format, v...)
   330  		if _, file, line, ok := runtime.Caller(1); ok {
   331  			index := strings.LastIndex(file, "/")
   332  			if index >= 0 {
   333  				file = file[index+1:]
   334  			}
   335  			msg = fmt.Sprintf("%s:%d|%s", file, line, msg)
   336  		}
   337  		prefix := logLevelMap[level]
   338  		defer func() {
   339  			_ = recover()
   340  			// ignore ch closed error
   341  		}()
   342  		if consoleLogger != nil {
   343  			consoleLogger.Printf("%s%s", prefix, msg)
   344  		}
   345  		if fileLogger != nil {
   346  			nowDate := fileLogger.formatLoggerNow("2006-01-02T15:04:05.000ZZ")
   347  			fileLogger.Printf("%s %s%s", nowDate, prefix, msg)
   348  		}
   349  	}
   350  }
   351  
   352  func checkAndLogErr(err error, level Level, format string, v ...interface{}) {
   353  	if err != nil {
   354  		doLog(level, format, v...)
   355  	}
   356  }
   357  
   358  func logResponseHeader(respHeader http.Header) string {
   359  	resp := make([]string, 0, len(respHeader)+1)
   360  	for key, value := range respHeader {
   361  		key = strings.TrimSpace(key)
   362  		if key == "" {
   363  			continue
   364  		}
   365  		if strings.HasPrefix(key, HEADER_PREFIX) || strings.HasPrefix(key, HEADER_PREFIX_OBS) {
   366  			key = key[len(HEADER_PREFIX):]
   367  		}
   368  		_key := strings.ToLower(key)
   369  		if _, ok := allowedLogResponseHTTPHeaderNames[_key]; ok {
   370  			resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0]))
   371  		}
   372  		if _key == HEADER_REQUEST_ID {
   373  			resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0]))
   374  		}
   375  	}
   376  	return strings.Join(resp, " ")
   377  }
   378  
   379  func logRequestHeader(reqHeader http.Header) string {
   380  	resp := make([]string, 0, len(reqHeader)+1)
   381  	for key, value := range reqHeader {
   382  		key = strings.TrimSpace(key)
   383  		if key == "" {
   384  			continue
   385  		}
   386  		_key := strings.ToLower(key)
   387  		if _, ok := allowedRequestHTTPHeaderMetadataNames[_key]; ok {
   388  			resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0]))
   389  		}
   390  	}
   391  	return strings.Join(resp, " ")
   392  }