logger.go raw
1 /*
2 * Copyright 2017 Baidu, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. 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 distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
13 */
14
15 // logger.go - defines the logger structure and methods
16
17 // Package log implements the log facilities for BCE. It supports log to stderr, stdout as well as
18 // log to file with rotating. It is safe to be called by multiple goroutines.
19 // By using the package level function to use the default logger:
20 // log.SetLogHandler(log.STDOUT | log.FILE) // default is log to stdout
21 // log.SetLogDir("/tmp") // default is /tmp
22 // log.SetRotateType(log.ROTATE_DAY) // default is log.HOUR
23 // log.SetRotateSize(1 << 30) // default is 1GB
24 // log.SetLogLevel(log.INFO) // default is log.DEBUG
25 // log.Debug(1, 1.2, "a")
26 // log.Debugln(1, 1.2, "a")
27 // log.Debugf(1, 1.2, "a")
28 // User can also create new logger without using the default logger:
29 // customLogger := log.NewLogger()
30 // customLogger.SetLogHandler(log.FILE)
31 // customLogger.Debug(1, 1.2, "a")
32 // The log format can also support custom setting by using the following interface:
33 // log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_TIME, log.FMT_MSG})
34 // Most of the cases just use the default format is enough:
35 // []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG}
36 package log
37
38 import (
39 "fmt"
40 "io"
41 "os"
42 "path/filepath"
43 "runtime"
44 "strings"
45 "time"
46 )
47
48 type Handler uint8
49
50 // Constants for log handler flags, default is STDOUT
51 const (
52 NONE Handler = 0
53 STDOUT Handler = 1
54 STDERR Handler = 1 << 1
55 FILE Handler = 1 << 2
56 )
57
58 type RotateStrategy uint8
59
60 // Constants for log rotating strategy when logging to file, default is by hour
61 const (
62 ROTATE_NONE RotateStrategy = iota
63 ROTATE_DAY
64 ROTATE_HOUR
65 ROTATE_MINUTE
66 ROTATE_SIZE
67
68 DEFAULT_ROTATE_TYPE = ROTATE_HOUR
69 DEFAULT_ROTATE_SIZE int64 = 1 << 30
70 DEFAULT_LOG_DIR = "/tmp"
71 ROTATE_SIZE_FILE_PREFIX = "rotating"
72 )
73
74 type Level uint8
75
76 // Constants for log levels, default is DEBUG
77 const (
78 DEBUG Level = iota
79 INFO
80 WARN
81 ERROR
82 FATAL
83 PANIC
84 )
85
86 var gLevelString = [...]string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"}
87
88 // Constants of the log format components to support user custom specification
89 const (
90 FMT_LEVEL = "level"
91 FMT_LTIME = "ltime" // long time with microsecond
92 FMT_TIME = "time" // just with second
93 FMT_LOCATION = "location" // caller's location with file, line, function
94 FMT_MSG = "msg"
95 )
96
97 var (
98 LOG_FMT_STR = map[string]string{
99 FMT_LEVEL: "[%s]",
100 FMT_LTIME: "2006-01-02 15:04:05.000000",
101 FMT_TIME: "2006-01-02 15:04:05",
102 FMT_LOCATION: "%s:%d:%s:",
103 FMT_MSG: "%s",
104 }
105 gDefaultLogFormat = []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG}
106 )
107
108 type writerArgs struct {
109 record string
110 rotateArgs interface{} // used for rotating: the size of the record or the logging time
111 }
112
113 // Logger defines the internal implementation of the log facility
114 type logger struct {
115 writers map[Handler]io.WriteCloser // the destination writer to log message
116 writerChan chan *writerArgs // the writer channal to pass each record and time or size
117 logFormat []string
118 levelThreshold Level
119 handler Handler
120
121 // Fields that used when logging to file
122 logDir string
123 logFile string
124 rotateType RotateStrategy
125 rotateSize int64
126 done chan bool
127 }
128
129 func (l *logger) logging(level Level, format string, args ...interface{}) {
130 // Only log message that set the handler and is greater than or equal to the threshold
131 if l.handler == NONE || level < l.levelThreshold {
132 return
133 }
134
135 // Generate the log record string and pass it to the writer channel
136 now := time.Now()
137 pc, file, line, ok, funcname := uintptr(0), "???", 0, true, "???"
138 pc, file, line, ok = runtime.Caller(2)
139 if ok {
140 funcname = runtime.FuncForPC(pc).Name()
141 funcname = filepath.Ext(funcname)
142 funcname = strings.TrimPrefix(funcname, ".")
143 file = filepath.Base(file)
144 }
145 buf := make([]string, 0, len(l.logFormat))
146 msg := fmt.Sprintf(format, args...)
147 for _, f := range l.logFormat {
148 if _, exists := LOG_FMT_STR[f]; !exists { // skip not supported part
149 continue
150 }
151 fmtStr := LOG_FMT_STR[f]
152 switch f {
153 case FMT_LEVEL:
154 buf = append(buf, fmt.Sprintf(fmtStr, gLevelString[level]))
155 case FMT_LTIME:
156 buf = append(buf, now.Format(fmtStr))
157 case FMT_TIME:
158 buf = append(buf, now.Format(fmtStr))
159 case FMT_LOCATION:
160 buf = append(buf, fmt.Sprintf(fmtStr, file, line, funcname))
161 case FMT_MSG:
162 buf = append(buf, fmt.Sprintf(fmtStr, msg))
163 }
164 }
165 record := strings.Join(buf, " ")
166 if l.rotateType == ROTATE_SIZE {
167 l.writerChan <- &writerArgs{record, int64(len(record))}
168 } else {
169 l.writerChan <- &writerArgs{record, now}
170 }
171
172 // wait for current record done logging
173 }
174
175 func (l *logger) buildWriter(args interface{}) {
176 if l.handler&STDOUT == STDOUT {
177 l.writers[STDOUT] = os.Stdout
178 } else {
179 delete(l.writers, STDOUT)
180 }
181 if l.handler&STDERR == STDERR {
182 l.writers[STDERR] = os.Stderr
183 } else {
184 delete(l.writers, STDERR)
185 }
186 if l.handler&FILE == FILE {
187 l.writers[FILE] = l.buildFileWriter(args)
188 } else {
189 delete(l.writers, FILE)
190 }
191 }
192
193 func (l *logger) buildFileWriter(args interface{}) io.WriteCloser {
194 if l.handler&FILE != FILE {
195 return os.Stderr
196 }
197
198 if len(l.logDir) == 0 {
199 l.logDir = DEFAULT_LOG_DIR
200 }
201 if l.rotateType < ROTATE_NONE || l.rotateType > ROTATE_SIZE {
202 l.rotateType = DEFAULT_ROTATE_TYPE
203 }
204 if l.rotateType == ROTATE_SIZE && l.rotateSize == 0 {
205 l.rotateSize = DEFAULT_ROTATE_SIZE
206 }
207
208 logFile, needCreateFile := "", false
209 if l.rotateType == ROTATE_SIZE {
210 recordSize, _ := args.(int64)
211 logFile, needCreateFile = l.buildFileWriterBySize(recordSize)
212 } else {
213 recordTime, _ := args.(time.Time)
214 switch l.rotateType {
215 case ROTATE_NONE:
216 logFile = "default.log"
217 case ROTATE_DAY:
218 logFile = recordTime.Format("2006-01-02.log")
219 case ROTATE_HOUR:
220 logFile = recordTime.Format("2006-01-02_15.log")
221 case ROTATE_MINUTE:
222 logFile = recordTime.Format("2006-01-02_15-04.log")
223 }
224 if _, exist := getFileInfo(filepath.Join(l.logDir, logFile)); !exist {
225 needCreateFile = true
226 }
227 }
228 l.logFile = logFile
229 logFile = filepath.Join(l.logDir, l.logFile)
230
231 // Should create new file
232 if needCreateFile {
233 if w, ok := l.writers[FILE]; ok {
234 w.Close()
235 }
236 if writer, err := os.Create(logFile); err == nil {
237 return writer
238 } else {
239 return os.Stderr
240 }
241 }
242
243 // Already open the file
244 if w, ok := l.writers[FILE]; ok {
245 return w
246 }
247
248 // Newly open the file
249 if writer, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND, 0666); err == nil {
250 return writer
251 } else {
252 return os.Stderr
253 }
254 }
255
256 func (l *logger) buildFileWriterBySize(recordSize int64) (string, bool) {
257 logFile, needCreateFile := "", false
258 // First running the program and need to get filename by checking the existed files
259 if len(l.logFile) == 0 {
260 fname := fmt.Sprintf("%s-%s.0.log", ROTATE_SIZE_FILE_PREFIX, getSizeString(l.rotateSize))
261 for {
262 size, exist := getFileInfo(filepath.Join(l.logDir, fname))
263 if !exist {
264 logFile, needCreateFile = fname, true
265 break
266 }
267 if exist && size+recordSize <= l.rotateSize {
268 logFile, needCreateFile = fname, false
269 break
270 }
271 fname = getNextFileName(fname)
272 }
273 } else { // check the file size to append to the existed file or create a new file
274 currentFile := filepath.Join(l.logDir, l.logFile)
275 size, exist := getFileInfo(currentFile)
276 if !exist {
277 logFile, needCreateFile = l.logFile, true
278 } else {
279 if size+recordSize > l.rotateSize { // size exceeded
280 logFile, needCreateFile = getNextFileName(l.logFile), true
281 } else {
282 logFile, needCreateFile = l.logFile, false
283 }
284 }
285 }
286 return logFile, needCreateFile
287 }
288
289 func (l *logger) SetHandler(h Handler) { l.handler = h }
290
291 func (l *logger) SetLogDir(dir string) { l.logDir = dir }
292
293 func (l *logger) SetLogLevel(level Level) { l.levelThreshold = level }
294
295 func (l *logger) SetLogFormat(format []string) { l.logFormat = format }
296
297 func (l *logger) SetRotateType(rotate RotateStrategy) { l.rotateType = rotate }
298
299 func (l *logger) SetRotateSize(size int64) { l.rotateSize = size }
300
301 func (l *logger) Debug(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) }
302
303 func (l *logger) Debugln(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) }
304
305 func (l *logger) Debugf(f string, msg ...interface{}) { l.logging(DEBUG, f+"\n", msg...) }
306
307 func (l *logger) Info(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) }
308
309 func (l *logger) Infoln(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) }
310
311 func (l *logger) Infof(f string, msg ...interface{}) { l.logging(INFO, f+"\n", msg...) }
312
313 func (l *logger) Warn(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) }
314
315 func (l *logger) Warnln(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) }
316
317 func (l *logger) Warnf(f string, msg ...interface{}) { l.logging(WARN, f+"\n", msg...) }
318
319 func (l *logger) Error(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) }
320
321 func (l *logger) Errorln(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) }
322
323 func (l *logger) Errorf(f string, msg ...interface{}) { l.logging(ERROR, f+"\n", msg...) }
324
325 func (l *logger) Fatal(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) }
326
327 func (l *logger) Fatalln(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) }
328
329 func (l *logger) Fatalf(f string, msg ...interface{}) { l.logging(FATAL, f+"\n", msg...) }
330
331 func (l *logger) Panic(msg ...interface{}) {
332 record := concat(msg...)
333 l.logging(PANIC, "%s\n", record)
334 panic(record)
335 }
336
337 func (l *logger) Panicln(msg ...interface{}) {
338 record := concat(msg...)
339 l.logging(PANIC, "%s\n", record)
340 panic(record)
341 }
342
343 func (l *logger) Panicf(format string, msg ...interface{}) {
344 record := fmt.Sprintf(format, msg...)
345 l.logging(PANIC, format+"\n", msg...)
346 panic(record)
347 }
348
349 func (l *logger) Close() {
350 select {
351 case <-l.done:
352 return
353 default:
354 }
355 l.writerChan <- nil
356 }
357
358 func NewLogger() *logger {
359 obj := &logger{
360 writers: make(map[Handler]io.WriteCloser, 3), // now only support 3 kinds of handler
361 writerChan: make(chan *writerArgs, 100),
362 logFormat: gDefaultLogFormat,
363 levelThreshold: DEBUG,
364 handler: NONE,
365 done: make(chan bool),
366 }
367 // The backend writer goroutine to write each log record
368 go func() {
369 defer func() {
370 if e := recover(); e != nil {
371 fmt.Println(e)
372 }
373 }()
374 for {
375 select {
376 case <-obj.done:
377 return
378 case args := <-obj.writerChan: // wait until a record comes to log
379 if args == nil {
380 close(obj.done)
381 close(obj.writerChan)
382 return
383 }
384 obj.buildWriter(args.rotateArgs)
385 for _, w := range obj.writers {
386 fmt.Fprint(w, args.record)
387 }
388 }
389 }
390 }()
391
392 return obj
393 }
394