github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/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  	"os"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  	"sync"
    23  )
    24  
    25  // Level defines the level of the log
    26  type Level int
    27  
    28  const (
    29  	LEVEL_OFF   Level = 500
    30  	LEVEL_ERROR Level = 400
    31  	LEVEL_WARN  Level = 300
    32  	LEVEL_INFO  Level = 200
    33  	LEVEL_DEBUG Level = 100
    34  )
    35  
    36  var logLevelMap = map[Level]string{
    37  	LEVEL_OFF:   "[OFF]: ",
    38  	LEVEL_ERROR: "[ERROR]: ",
    39  	LEVEL_WARN:  "[WARN]: ",
    40  	LEVEL_INFO:  "[INFO]: ",
    41  	LEVEL_DEBUG: "[DEBUG]: ",
    42  }
    43  
    44  type logConfType struct {
    45  	level        Level
    46  	logToConsole bool
    47  	logFullPath  string
    48  	maxLogSize   int64
    49  	backups      int
    50  }
    51  
    52  func getDefaultLogConf() logConfType {
    53  	return logConfType{
    54  		level:        LEVEL_WARN,
    55  		logToConsole: false,
    56  		logFullPath:  "",
    57  		maxLogSize:   1024 * 1024 * 30, //30MB
    58  		backups:      10,
    59  	}
    60  }
    61  
    62  var logConf logConfType
    63  
    64  type loggerWrapper struct {
    65  	fullPath   string
    66  	fd         *os.File
    67  	ch         chan string
    68  	wg         sync.WaitGroup
    69  	queue      []string
    70  	logger     *log.Logger
    71  	index      int
    72  	cacheCount int
    73  	closed     bool
    74  }
    75  
    76  func (lw *loggerWrapper) doInit() {
    77  	lw.queue = make([]string, 0, lw.cacheCount)
    78  	lw.logger = log.New(lw.fd, "", 0)
    79  	lw.ch = make(chan string, lw.cacheCount)
    80  	lw.wg.Add(1)
    81  	go lw.doWrite()
    82  }
    83  
    84  func (lw *loggerWrapper) rotate() {
    85  	stat, err := lw.fd.Stat()
    86  	if err != nil {
    87  		_err := lw.fd.Close()
    88  		if _err != nil {
    89  			doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
    90  		}
    91  		panic(err)
    92  	}
    93  	if stat.Size() >= logConf.maxLogSize {
    94  		_err := lw.fd.Sync()
    95  		if _err != nil {
    96  			panic(err)
    97  		}
    98  		_err = lw.fd.Close()
    99  		if _err != nil {
   100  			doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   101  		}
   102  		if lw.index > logConf.backups {
   103  			lw.index = 1
   104  		}
   105  		_err = os.Rename(lw.fullPath, lw.fullPath+"."+IntToString(lw.index))
   106  		if _err != nil {
   107  			panic(err)
   108  		}
   109  		lw.index++
   110  
   111  		fd, err := os.OpenFile(lw.fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
   112  		if err != nil {
   113  			panic(err)
   114  		}
   115  		lw.fd = fd
   116  		lw.logger.SetOutput(lw.fd)
   117  	}
   118  }
   119  
   120  func (lw *loggerWrapper) doFlush() {
   121  	lw.rotate()
   122  	for _, m := range lw.queue {
   123  		lw.logger.Println(m)
   124  	}
   125  	err := lw.fd.Sync()
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  }
   130  
   131  func (lw *loggerWrapper) doClose() {
   132  	lw.closed = true
   133  	close(lw.ch)
   134  	lw.wg.Wait()
   135  }
   136  
   137  func (lw *loggerWrapper) doWrite() {
   138  	defer lw.wg.Done()
   139  	for {
   140  		msg, ok := <-lw.ch
   141  		if !ok {
   142  			lw.doFlush()
   143  			_err := lw.fd.Close()
   144  			if _err != nil {
   145  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   146  			}
   147  			break
   148  		}
   149  		if len(lw.queue) >= lw.cacheCount {
   150  			lw.doFlush()
   151  			lw.queue = make([]string, 0, lw.cacheCount)
   152  		}
   153  		lw.queue = append(lw.queue, msg)
   154  	}
   155  
   156  }
   157  
   158  func (lw *loggerWrapper) Printf(format string, v ...interface{}) {
   159  	if !lw.closed {
   160  		msg := fmt.Sprintf(format, v...)
   161  		lw.ch <- msg
   162  	}
   163  }
   164  
   165  var consoleLogger *log.Logger
   166  var fileLogger *loggerWrapper
   167  var lock = new(sync.RWMutex)
   168  
   169  func isDebugLogEnabled() bool {
   170  	return logConf.level <= LEVEL_DEBUG
   171  }
   172  
   173  func isErrorLogEnabled() bool {
   174  	return logConf.level <= LEVEL_ERROR
   175  }
   176  
   177  func isWarnLogEnabled() bool {
   178  	return logConf.level <= LEVEL_WARN
   179  }
   180  
   181  func isInfoLogEnabled() bool {
   182  	return logConf.level <= LEVEL_INFO
   183  }
   184  
   185  func reset() {
   186  	if fileLogger != nil {
   187  		fileLogger.doClose()
   188  		fileLogger = nil
   189  	}
   190  	consoleLogger = nil
   191  	logConf = getDefaultLogConf()
   192  }
   193  
   194  // InitLog enable logging function with default cacheCnt
   195  func InitLog(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool) error {
   196  	return InitLogWithCacheCnt(logFullPath, maxLogSize, backups, level, logToConsole, 50)
   197  }
   198  
   199  // InitLogWithCacheCnt enable logging function
   200  func InitLogWithCacheCnt(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, cacheCnt int) error {
   201  	lock.Lock()
   202  	defer lock.Unlock()
   203  	if cacheCnt <= 0 {
   204  		cacheCnt = 50
   205  	}
   206  	reset()
   207  	if fullPath := strings.TrimSpace(logFullPath); fullPath != "" {
   208  		_fullPath, err := filepath.Abs(fullPath)
   209  		if err != nil {
   210  			return err
   211  		}
   212  
   213  		if !strings.HasSuffix(_fullPath, ".log") {
   214  			_fullPath += ".log"
   215  		}
   216  
   217  		stat, fd, err := initLogFile(_fullPath)
   218  		if err != nil {
   219  			return err
   220  		}
   221  
   222  		prefix := stat.Name() + "."
   223  		index := 1
   224  		var timeIndex int64 = 0
   225  		walkFunc := func(path string, info os.FileInfo, err error) error {
   226  			if err == nil {
   227  				if name := info.Name(); strings.HasPrefix(name, prefix) {
   228  					if i := StringToInt(name[len(prefix):], 0); i >= index && info.ModTime().Unix() >= timeIndex {
   229  						timeIndex = info.ModTime().Unix()
   230  						index = i + 1
   231  					}
   232  				}
   233  			}
   234  			return err
   235  		}
   236  
   237  		if err = filepath.Walk(filepath.Dir(_fullPath), walkFunc); err != nil {
   238  			_err := fd.Close()
   239  			if _err != nil {
   240  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   241  			}
   242  			return err
   243  		}
   244  
   245  		fileLogger = &loggerWrapper{fullPath: _fullPath, fd: fd, index: index, cacheCount: cacheCnt, closed: false}
   246  		fileLogger.doInit()
   247  	}
   248  	if maxLogSize > 0 {
   249  		logConf.maxLogSize = maxLogSize
   250  	}
   251  	if backups > 0 {
   252  		logConf.backups = backups
   253  	}
   254  	logConf.level = level
   255  	if logToConsole {
   256  		consoleLogger = log.New(os.Stdout, "", log.LstdFlags)
   257  	}
   258  	return nil
   259  }
   260  
   261  func initLogFile(_fullPath string) (os.FileInfo, *os.File, error) {
   262  	stat, err := os.Stat(_fullPath)
   263  	if err == nil && stat.IsDir() {
   264  		return nil, nil, fmt.Errorf("logFullPath:[%s] is a directory", _fullPath)
   265  	} else if err = os.MkdirAll(filepath.Dir(_fullPath), os.ModePerm); err != nil {
   266  		return nil, nil, err
   267  	}
   268  
   269  	fd, err := os.OpenFile(_fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
   270  	if err != nil {
   271  		return nil, nil, err
   272  	}
   273  
   274  	if stat == nil {
   275  		stat, err = os.Stat(_fullPath)
   276  		if err != nil {
   277  			_err := fd.Close()
   278  			if _err != nil {
   279  				doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err)
   280  			}
   281  			return nil, nil, err
   282  		}
   283  	}
   284  	return stat, fd, nil
   285  }
   286  
   287  // CloseLog disable logging and synchronize cache data to log files
   288  func CloseLog() {
   289  	if logEnabled() {
   290  		lock.Lock()
   291  		defer lock.Unlock()
   292  		reset()
   293  	}
   294  }
   295  
   296  func logEnabled() bool {
   297  	return consoleLogger != nil || fileLogger != nil
   298  }
   299  
   300  // DoLog writes log messages to the logger
   301  func DoLog(level Level, format string, v ...interface{}) {
   302  	doLog(level, format, v...)
   303  }
   304  
   305  func doLog(level Level, format string, v ...interface{}) {
   306  	if logEnabled() && logConf.level <= level {
   307  		msg := fmt.Sprintf(format, v...)
   308  		if _, file, line, ok := runtime.Caller(1); ok {
   309  			index := strings.LastIndex(file, "/")
   310  			if index >= 0 {
   311  				file = file[index+1:]
   312  			}
   313  			msg = fmt.Sprintf("%s:%d|%s", file, line, msg)
   314  		}
   315  		prefix := logLevelMap[level]
   316  		if consoleLogger != nil {
   317  			consoleLogger.Printf("%s%s", prefix, msg)
   318  		}
   319  		if fileLogger != nil {
   320  			nowDate := FormatUtcNow("2006-01-02T15:04:05Z")
   321  			fileLogger.Printf("%s %s%s", nowDate, prefix, msg)
   322  		}
   323  	}
   324  }
   325  
   326  func checkAndLogErr(err error, level Level, format string, v ...interface{}) {
   327  	if err != nil {
   328  		doLog(level, format, v...)
   329  	}
   330  }