github.com/sujit-baniya/log@v1.0.73/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  				fmt.Fprintf(b, " %s=%s", kv.Key, strconv.Quote(kv.Value))
   156  			} else {
   157  				fmt.Fprintf(b, " %s=%s", kv.Key, kv.Value)
   158  			}
   159  		}
   160  		// message
   161  		if w.EndWithMessage {
   162  			fmt.Fprintf(b, " %s", args.Message)
   163  		}
   164  	}
   165  
   166  	// stack
   167  	if args.Stack != "" {
   168  		b.B = append(b.B, '\n')
   169  		b.B = append(b.B, args.Stack...)
   170  		if args.Stack[len(args.Stack)-1] != '\n' {
   171  			b.B = append(b.B, '\n')
   172  		}
   173  	} else {
   174  		b.B = append(b.B, '\n')
   175  	}
   176  	return out.Write(b.B)
   177  }
   178  
   179  type LogfmtFormatter struct {
   180  	TimeField string
   181  }
   182  
   183  func (f LogfmtFormatter) Formatter(out io.Writer, args *FormatterArgs) (n int, err error) {
   184  	b := bbpool.Get().(*bb)
   185  	b.B = b.B[:0]
   186  	defer bbpool.Put(b)
   187  
   188  	fmt.Fprintf(b, "%s=%s ", f.TimeField, args.Time)
   189  	if args.Level != "" && args.Level[0] != '?' {
   190  		fmt.Fprintf(b, "level=%s ", args.Level)
   191  	}
   192  	if args.Caller != "" {
   193  		fmt.Fprintf(b, "goid=%s caller=%s ", args.Goid, strconv.Quote(args.Caller))
   194  	}
   195  	if args.Stack != "" {
   196  		fmt.Fprintf(b, "stack=%s ", strconv.Quote(args.Stack))
   197  	}
   198  	// key and values
   199  	for _, kv := range args.KeyValues {
   200  		switch kv.ValueType {
   201  		case 't':
   202  			fmt.Fprintf(b, "%s ", kv.Key)
   203  		case 'f':
   204  			fmt.Fprintf(b, "%s=false ", kv.Key)
   205  		case 'n':
   206  			fmt.Fprintf(b, "%s=%s ", kv.Key, kv.Value)
   207  		case 'S':
   208  			fmt.Fprintf(b, "%s=%s ", kv.Key, kv.Value)
   209  		case 's':
   210  			fmt.Fprintf(b, "%s=%s ", kv.Key, strconv.Quote(kv.Value))
   211  		default:
   212  			fmt.Fprintf(b, "%s=%s ", kv.Key, strconv.Quote(kv.Value))
   213  		}
   214  	}
   215  	// message
   216  	fmt.Fprintf(b, "%s\n", strconv.Quote(args.Message))
   217  
   218  	return out.Write(b.B)
   219  }
   220  
   221  var _ Writer = (*ConsoleWriter)(nil)