github.com/phuslu/log@v1.0.100/console.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"runtime"
     7  	"strconv"
     8  )
     9  
    10  // IsTerminal returns whether the given file descriptor is a terminal.
    11  func IsTerminal(fd uintptr) bool {
    12  	return isTerminal(fd, runtime.GOOS, runtime.GOARCH)
    13  }
    14  
    15  // ConsoleWriter parses the JSON input and writes it in a colorized, human-friendly format to Writer.
    16  // IMPORTANT: Don't use ConsoleWriter on critical path of a high concurrency and low latency application.
    17  //
    18  // Default output format:
    19  //
    20  //	{Time} {Level} {Goid} {Caller} > {Message} {Key}={Value} {Key}={Value}
    21  //
    22  // Note: The performance of ConsoleWriter is not good enough, because it will
    23  // parses JSON input into structured records, then output in a specific order.
    24  // Roughly 2x faster than logrus.TextFormatter, 0.8x fast as zap.ConsoleEncoder,
    25  // and 5x faster than zerolog.ConsoleWriter.
    26  type ConsoleWriter struct {
    27  	// ColorOutput determines if used colorized output.
    28  	ColorOutput bool
    29  
    30  	// QuoteString determines if quoting string values.
    31  	QuoteString bool
    32  
    33  	// EndWithMessage determines if output message in the end.
    34  	EndWithMessage bool
    35  
    36  	// Formatter specifies an optional text formatter for creating a customized output,
    37  	// If it is set, ColorOutput, QuoteString and EndWithMessage will be ignore.
    38  	Formatter func(w io.Writer, args *FormatterArgs) (n int, err error)
    39  
    40  	// Writer is the output destination. using os.Stderr if empty.
    41  	Writer io.Writer
    42  }
    43  
    44  // Close implements io.Closer, will closes the underlying Writer if not empty.
    45  func (w *ConsoleWriter) Close() (err error) {
    46  	if w.Writer != nil {
    47  		if closer, ok := w.Writer.(io.Closer); ok {
    48  			err = closer.Close()
    49  		}
    50  	}
    51  	return
    52  }
    53  
    54  func (w *ConsoleWriter) write(out io.Writer, p []byte) (int, error) {
    55  	b := bbpool.Get().(*bb)
    56  	b.B = b.B[:0]
    57  	defer bbpool.Put(b)
    58  
    59  	b.B = append(b.B, p...)
    60  
    61  	var args FormatterArgs
    62  	parseFormatterArgs(b.B, &args)
    63  
    64  	switch {
    65  	case args.Time == "":
    66  		return out.Write(p)
    67  	case w.Formatter != nil:
    68  		return w.Formatter(out, &args)
    69  	default:
    70  		return w.format(out, &args)
    71  	}
    72  
    73  }
    74  
    75  func (w *ConsoleWriter) format(out io.Writer, args *FormatterArgs) (n int, err error) {
    76  	b := bbpool.Get().(*bb)
    77  	b.B = b.B[:0]
    78  	defer bbpool.Put(b)
    79  
    80  	const (
    81  		Reset   = "\x1b[0m"
    82  		Black   = "\x1b[30m"
    83  		Red     = "\x1b[31m"
    84  		Green   = "\x1b[32m"
    85  		Yellow  = "\x1b[33m"
    86  		Blue    = "\x1b[34m"
    87  		Magenta = "\x1b[35m"
    88  		Cyan    = "\x1b[36m"
    89  		White   = "\x1b[37m"
    90  		Gray    = "\x1b[90m"
    91  	)
    92  
    93  	// colorful level string
    94  	var color, three string
    95  	switch args.Level {
    96  	case "trace":
    97  		color, three = Magenta, "TRC"
    98  	case "debug":
    99  		color, three = Yellow, "DBG"
   100  	case "info":
   101  		color, three = Green, "INF"
   102  	case "warn":
   103  		color, three = Red, "WRN"
   104  	case "error":
   105  		color, three = Red, "ERR"
   106  	case "fatal":
   107  		color, three = Red, "FTL"
   108  	case "panic":
   109  		color, three = Red, "PNC"
   110  	default:
   111  		color, three = Gray, "???"
   112  	}
   113  
   114  	// pretty console writer
   115  	if w.ColorOutput {
   116  		// header
   117  		fmt.Fprintf(b, "%s%s%s %s%s%s ", Gray, args.Time, Reset, color, three, Reset)
   118  		if args.Caller != "" {
   119  			fmt.Fprintf(b, "%s %s %s>%s", args.Goid, args.Caller, Cyan, Reset)
   120  		} else {
   121  			fmt.Fprintf(b, "%s>%s", Cyan, Reset)
   122  		}
   123  		if !w.EndWithMessage {
   124  			fmt.Fprintf(b, " %s", args.Message)
   125  		}
   126  		// key and values
   127  		for _, kv := range args.KeyValues {
   128  			if w.QuoteString && kv.ValueType == 's' {
   129  				kv.Value = strconv.Quote(kv.Value)
   130  			}
   131  			if kv.Key == "error" {
   132  				fmt.Fprintf(b, " %s%s=%s%s", Red, kv.Key, kv.Value, Reset)
   133  			} else {
   134  				fmt.Fprintf(b, " %s%s=%s%s%s", Cyan, kv.Key, Gray, kv.Value, Reset)
   135  			}
   136  		}
   137  		// message
   138  		if w.EndWithMessage {
   139  			fmt.Fprintf(b, "%s %s", Reset, args.Message)
   140  		}
   141  	} else {
   142  		// header
   143  		fmt.Fprintf(b, "%s %s ", args.Time, three)
   144  		if args.Caller != "" {
   145  			fmt.Fprintf(b, "%s %s >", args.Goid, args.Caller)
   146  		} else {
   147  			fmt.Fprint(b, ">")
   148  		}
   149  		if !w.EndWithMessage {
   150  			fmt.Fprintf(b, " %s", args.Message)
   151  		}
   152  		// key and values
   153  		for _, kv := range args.KeyValues {
   154  			if w.QuoteString && kv.ValueType == 's' {
   155  				b.B = append(b.B, ' ')
   156  				b.B = append(b.B, kv.Key...)
   157  				b.B = append(b.B, '=')
   158  				b.B = strconv.AppendQuote(b.B, kv.Value)
   159  			} else {
   160  				fmt.Fprintf(b, " %s=%s", kv.Key, kv.Value)
   161  			}
   162  		}
   163  		// message
   164  		if w.EndWithMessage {
   165  			fmt.Fprintf(b, " %s", args.Message)
   166  		}
   167  	}
   168  
   169  	// add line break if needed
   170  	if b.B[len(b.B)-1] != '\n' {
   171  		b.B = append(b.B, '\n')
   172  	}
   173  
   174  	// stack
   175  	if args.Stack != "" {
   176  		b.B = append(b.B, args.Stack...)
   177  		if args.Stack[len(args.Stack)-1] != '\n' {
   178  			b.B = append(b.B, '\n')
   179  		}
   180  	}
   181  
   182  	return out.Write(b.B)
   183  }
   184  
   185  type LogfmtFormatter struct {
   186  	TimeField string
   187  }
   188  
   189  func (f LogfmtFormatter) Formatter(out io.Writer, args *FormatterArgs) (n int, err error) {
   190  	b := bbpool.Get().(*bb)
   191  	b.B = b.B[:0]
   192  	defer bbpool.Put(b)
   193  
   194  	fmt.Fprintf(b, "%s=%s ", f.TimeField, args.Time)
   195  	if args.Level != "" && args.Level[0] != '?' {
   196  		fmt.Fprintf(b, "level=%s ", args.Level)
   197  	}
   198  	if args.Caller != "" {
   199  		fmt.Fprintf(b, "goid=%s caller=", args.Goid)
   200  		b.B = strconv.AppendQuote(b.B, args.Caller)
   201  		b.B = append(b.B, ' ')
   202  	}
   203  	if args.Stack != "" {
   204  		b.B = append(b.B, "stack="...)
   205  		b.B = strconv.AppendQuote(b.B, args.Stack)
   206  		b.B = append(b.B, ' ')
   207  	}
   208  	// key and values
   209  	for _, kv := range args.KeyValues {
   210  		switch kv.ValueType {
   211  		case 't':
   212  			fmt.Fprintf(b, "%s ", kv.Key)
   213  		case 'f':
   214  			fmt.Fprintf(b, "%s=false ", kv.Key)
   215  		case 'n':
   216  			fmt.Fprintf(b, "%s=%s ", kv.Key, kv.Value)
   217  		case 'S':
   218  			fmt.Fprintf(b, "%s=%s ", kv.Key, kv.Value)
   219  		case 's':
   220  			fallthrough
   221  		default:
   222  			b.B = append(b.B, kv.Key...)
   223  			b.B = append(b.B, '=')
   224  			b.B = strconv.AppendQuote(b.B, kv.Value)
   225  			b.B = append(b.B, ' ')
   226  		}
   227  	}
   228  	// message
   229  	b.B = strconv.AppendQuote(b.B, args.Message)
   230  	b.B = append(b.B, '\n')
   231  
   232  	return out.Write(b.B)
   233  }
   234  
   235  var _ Writer = (*ConsoleWriter)(nil)