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  }