github.com/zhongdalu/gf@v1.0.0/g/os/glog/glog_logger.go (about) 1 // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/zhongdalu/gf. 6 7 package glog 8 9 import ( 10 "bytes" 11 "errors" 12 "fmt" 13 "io" 14 "os" 15 "strings" 16 "time" 17 18 "github.com/zhongdalu/gf/g/internal/debug" 19 20 "github.com/zhongdalu/gf/g/os/gfile" 21 "github.com/zhongdalu/gf/g/os/gfpool" 22 "github.com/zhongdalu/gf/g/os/gtime" 23 "github.com/zhongdalu/gf/g/text/gregex" 24 "github.com/zhongdalu/gf/g/util/gconv" 25 ) 26 27 type Logger struct { 28 parent *Logger // Parent logger. 29 writer io.Writer // Customized io.Writer. 30 flags int // Extra flags for logging output features. 31 path string // Logging directory path. 32 file string // Format for logging file. 33 level int // Output level. 34 prefix string // Prefix string for every logging content. 35 stSkip int // Skip count for stack. 36 stStatus int // Stack status(1: enabled - default; 0: disabled) 37 headerPrint bool // Print header or not(true in default). 38 stdoutPrint bool // Output to stdout or not(true in default). 39 } 40 41 const ( 42 gDEFAULT_FILE_FORMAT = `{Y-m-d}.log` 43 gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE | os.O_WRONLY | os.O_APPEND 44 gDEFAULT_FPOOL_PERM = os.FileMode(0666) 45 gDEFAULT_FPOOL_EXPIRE = 60000 46 gPATH_FILTER_KEY = "/g/os/glog/glog" 47 ) 48 49 const ( 50 F_ASYNC = 1 << iota // Print logging content asynchronously。 51 F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23. 52 F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG. 53 F_TIME_DATE // Print the date in the local time zone: 2009-01-23. 54 F_TIME_TIME // Print the time in the local time zone: 01:23:23. 55 F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675. 56 F_TIME_STD = F_TIME_DATE | F_TIME_MILLI 57 ) 58 59 // New creates and returns a custom logger. 60 func New() *Logger { 61 logger := &Logger{ 62 file: gDEFAULT_FILE_FORMAT, 63 flags: F_TIME_STD, 64 level: LEVEL_ALL, 65 stStatus: 1, 66 headerPrint: true, 67 stdoutPrint: true, 68 } 69 return logger 70 } 71 72 // Clone returns a new logger, which is the clone the current logger. 73 func (l *Logger) Clone() *Logger { 74 logger := Logger{} 75 logger = *l 76 logger.parent = l 77 return &logger 78 } 79 80 // SetLevel sets the logging level. 81 func (l *Logger) SetLevel(level int) { 82 l.level = level 83 } 84 85 // GetLevel returns the logging level value. 86 func (l *Logger) GetLevel() int { 87 return l.level 88 } 89 90 // SetDebug enables/disables the debug level for logger. 91 // The debug level is enabled in default. 92 func (l *Logger) SetDebug(debug bool) { 93 if debug { 94 l.level = l.level | LEVEL_DEBU 95 } else { 96 l.level = l.level & ^LEVEL_DEBU 97 } 98 } 99 100 // SetAsync enables/disables async logging output feature. 101 func (l *Logger) SetAsync(enabled bool) { 102 if enabled { 103 l.flags = l.flags | F_ASYNC 104 } else { 105 l.flags = l.flags & ^F_ASYNC 106 } 107 } 108 109 // SetFlags sets extra flags for logging output features. 110 func (l *Logger) SetFlags(flags int) { 111 l.flags = flags 112 } 113 114 // GetFlags returns the flags of logger. 115 func (l *Logger) GetFlags() int { 116 return l.flags 117 } 118 119 // SetStack enables/disables the stack feature in failure logging outputs. 120 func (l *Logger) SetStack(enabled bool) { 121 if enabled { 122 l.stStatus = 1 123 } else { 124 l.stStatus = 0 125 } 126 } 127 128 // SetStackSkip sets the stack offset from the end point. 129 func (l *Logger) SetStackSkip(skip int) { 130 l.stSkip = skip 131 } 132 133 // SetWriter sets the customized logging <writer> for logging. 134 // The <writer> object should implements the io.Writer interface. 135 // Developer can use customized logging <writer> to redirect logging output to another service, 136 // eg: kafka, mysql, mongodb, etc. 137 func (l *Logger) SetWriter(writer io.Writer) { 138 l.writer = writer 139 } 140 141 // GetWriter returns the customized writer object, which implements the io.Writer interface. 142 // It returns nil if no writer previously set. 143 func (l *Logger) GetWriter() io.Writer { 144 return l.writer 145 } 146 147 // getFilePointer returns the file pinter for file logging. 148 // It returns nil if file logging is disabled, or file opening fails. 149 func (l *Logger) getFilePointer() *gfpool.File { 150 if path := l.path; path != "" { 151 // Content containing "{}" in the file name is formatted using gtime 152 file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file, func(s string) string { 153 return gtime.Now().Format(strings.Trim(s, "{}")) 154 }) 155 // Create path if it does not exist。 156 if !gfile.Exists(path) { 157 if err := gfile.Mkdir(path); err != nil { 158 fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error())) 159 return nil 160 } 161 } 162 if fp, err := gfpool.Open( 163 path+gfile.Separator+file, 164 gDEFAULT_FILE_POOL_FLAGS, 165 gDEFAULT_FPOOL_PERM, 166 gDEFAULT_FPOOL_EXPIRE); err == nil { 167 return fp 168 } else { 169 fmt.Fprintln(os.Stderr, err) 170 } 171 } 172 return nil 173 } 174 175 // SetPath sets the directory path for file logging. 176 func (l *Logger) SetPath(path string) error { 177 if path == "" { 178 return errors.New("path is empty") 179 } 180 if !gfile.Exists(path) { 181 if err := gfile.Mkdir(path); err != nil { 182 fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error())) 183 return err 184 } 185 } 186 l.path = strings.TrimRight(path, gfile.Separator) 187 return nil 188 } 189 190 // GetPath returns the logging directory path for file logging. 191 // It returns empty string if no directory path set. 192 func (l *Logger) GetPath() string { 193 return l.path 194 } 195 196 // SetFile sets the file name <pattern> for file logging. 197 // Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log. 198 // The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log 199 func (l *Logger) SetFile(pattern string) { 200 l.file = pattern 201 } 202 203 // SetStdoutPrint sets whether output the logging contents to stdout, which is true in default. 204 func (l *Logger) SetStdoutPrint(enabled bool) { 205 l.stdoutPrint = enabled 206 } 207 208 // SetHeaderPrint sets whether output header of the logging contents, which is true in default. 209 func (l *Logger) SetHeaderPrint(enabled bool) { 210 l.headerPrint = enabled 211 } 212 213 // SetPrefix sets prefix string for every logging content. 214 // Prefix is part of header, which means if header output is shut, no prefix will be output. 215 func (l *Logger) SetPrefix(prefix string) { 216 l.prefix = prefix 217 } 218 219 // print prints <s> to defined writer, logging file or passed <std>. 220 func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { 221 buffer := bytes.NewBuffer(nil) 222 if l.headerPrint { 223 // Time. 224 timeFormat := "" 225 if l.flags&F_TIME_DATE > 0 { 226 timeFormat += "2006-01-02 " 227 } 228 if l.flags&F_TIME_TIME > 0 { 229 timeFormat += "15:04:05 " 230 } 231 if l.flags&F_TIME_MILLI > 0 { 232 timeFormat += "15:04:05.000 " 233 } 234 if len(timeFormat) > 0 { 235 buffer.WriteString(time.Now().Format(timeFormat)) 236 } 237 // Lead string. 238 if len(lead) > 0 { 239 buffer.WriteString(lead) 240 if len(value) > 0 { 241 buffer.WriteByte(' ') 242 } 243 } 244 // Caller path. 245 callerPath := "" 246 if l.flags&F_FILE_LONG > 0 { 247 callerPath = debug.CallerWithFilter(gPATH_FILTER_KEY, l.stSkip) + ": " 248 } 249 if l.flags&F_FILE_SHORT > 0 { 250 callerPath = gfile.Basename(debug.CallerWithFilter(gPATH_FILTER_KEY, l.stSkip)) + ": " 251 } 252 if len(callerPath) > 0 { 253 buffer.WriteString(callerPath) 254 } 255 // Prefix. 256 if len(l.prefix) > 0 { 257 buffer.WriteString(l.prefix + " ") 258 } 259 } 260 // Convert value to string. 261 tempStr := "" 262 valueStr := "" 263 for _, v := range value { 264 if err, ok := v.(error); ok { 265 tempStr = fmt.Sprintf("%+v", err) 266 } else { 267 tempStr = gconv.String(v) 268 } 269 if len(valueStr) > 0 { 270 if valueStr[len(valueStr)-1] == '\n' { 271 // Remove one blank line(\n\n). 272 if tempStr[0] == '\n' { 273 valueStr += tempStr[1:] 274 } else { 275 valueStr += tempStr 276 } 277 } else { 278 valueStr += " " + tempStr 279 } 280 } else { 281 valueStr = tempStr 282 } 283 } 284 buffer.WriteString(valueStr + "\n") 285 if l.flags&F_ASYNC > 0 { 286 asyncPool.Add(func() { 287 l.printToWriter(std, buffer) 288 }) 289 } else { 290 l.printToWriter(std, buffer) 291 } 292 } 293 294 // printToWriter writes buffer to writer. 295 func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) { 296 if l.writer == nil { 297 if f := l.getFilePointer(); f != nil { 298 defer f.Close() 299 if _, err := io.WriteString(f, buffer.String()); err != nil { 300 fmt.Fprintln(os.Stderr, err.Error()) 301 } 302 } 303 // Allow output to stdout? 304 if l.stdoutPrint { 305 if _, err := std.Write(buffer.Bytes()); err != nil { 306 fmt.Fprintln(os.Stderr, err.Error()) 307 } 308 } 309 } else { 310 if _, err := l.writer.Write(buffer.Bytes()); err != nil { 311 fmt.Fprintln(os.Stderr, err.Error()) 312 } 313 } 314 } 315 316 // printStd prints content <s> without stack. 317 func (l *Logger) printStd(lead string, value ...interface{}) { 318 l.print(os.Stdout, lead, value...) 319 } 320 321 // printStd prints content <s> with stack check. 322 func (l *Logger) printErr(lead string, value ...interface{}) { 323 if l.stStatus == 1 { 324 if s := l.GetStack(); s != "" { 325 value = append(value, "\nStack:\n"+s) 326 } 327 } 328 // In matter of sequence, do not use stderr here, but use the same stdout. 329 l.print(os.Stdout, lead, value...) 330 } 331 332 // format formats <values> using fmt.Sprintf. 333 func (l *Logger) format(format string, value ...interface{}) string { 334 return fmt.Sprintf(format, value...) 335 } 336 337 // PrintStack prints the caller stack, 338 // the optional parameter <skip> specify the skipped stack offset from the end point. 339 func (l *Logger) PrintStack(skip ...int) { 340 if s := l.GetStack(skip...); s != "" { 341 l.Println("Stack:\n" + s) 342 } else { 343 l.Println() 344 } 345 } 346 347 // GetStack returns the caller stack content, 348 // the optional parameter <skip> specify the skipped stack offset from the end point. 349 func (l *Logger) GetStack(skip ...int) string { 350 number := 1 351 if len(skip) > 0 { 352 number = skip[0] + 1 353 } 354 return debug.StackWithFilter(gPATH_FILTER_KEY, number) 355 }