yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/log.go (about)

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