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 }