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 }