...

Source file src/github.com/rs/zerolog/console.go

Documentation: github.com/rs/zerolog

     1  package zerolog
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/mattn/go-colorable"
    17  )
    18  
    19  const (
    20  	colorBlack = iota + 30
    21  	colorRed
    22  	colorGreen
    23  	colorYellow
    24  	colorBlue
    25  	colorMagenta
    26  	colorCyan
    27  	colorWhite
    28  
    29  	colorBold     = 1
    30  	colorDarkGray = 90
    31  )
    32  
    33  var (
    34  	consoleBufPool = sync.Pool{
    35  		New: func() interface{} {
    36  			return bytes.NewBuffer(make([]byte, 0, 100))
    37  		},
    38  	}
    39  )
    40  
    41  const (
    42  	consoleDefaultTimeFormat = time.Kitchen
    43  )
    44  
    45  // Formatter transforms the input into a formatted string.
    46  type Formatter func(interface{}) string
    47  
    48  // ConsoleWriter parses the JSON input and writes it in an
    49  // (optionally) colorized, human-friendly format to Out.
    50  type ConsoleWriter struct {
    51  	// Out is the output destination.
    52  	Out io.Writer
    53  
    54  	// NoColor disables the colorized output.
    55  	NoColor bool
    56  
    57  	// TimeFormat specifies the format for timestamp in output.
    58  	TimeFormat string
    59  
    60  	// PartsOrder defines the order of parts in output.
    61  	PartsOrder []string
    62  
    63  	// PartsExclude defines parts to not display in output.
    64  	PartsExclude []string
    65  
    66  	// FieldsExclude defines contextual fields to not display in output.
    67  	FieldsExclude []string
    68  
    69  	FormatTimestamp     Formatter
    70  	FormatLevel         Formatter
    71  	FormatCaller        Formatter
    72  	FormatMessage       Formatter
    73  	FormatFieldName     Formatter
    74  	FormatFieldValue    Formatter
    75  	FormatErrFieldName  Formatter
    76  	FormatErrFieldValue Formatter
    77  
    78  	FormatExtra func(map[string]interface{}, *bytes.Buffer) error
    79  }
    80  
    81  // NewConsoleWriter creates and initializes a new ConsoleWriter.
    82  func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
    83  	w := ConsoleWriter{
    84  		Out:        os.Stdout,
    85  		TimeFormat: consoleDefaultTimeFormat,
    86  		PartsOrder: consoleDefaultPartsOrder(),
    87  	}
    88  
    89  	for _, opt := range options {
    90  		opt(&w)
    91  	}
    92  
    93  	// Fix color on Windows
    94  	if w.Out == os.Stdout || w.Out == os.Stderr {
    95  		w.Out = colorable.NewColorable(w.Out.(*os.File))
    96  	}
    97  
    98  	return w
    99  }
   100  
   101  // Write transforms the JSON input with formatters and appends to w.Out.
   102  func (w ConsoleWriter) Write(p []byte) (n int, err error) {
   103  	// Fix color on Windows
   104  	if w.Out == os.Stdout || w.Out == os.Stderr {
   105  		w.Out = colorable.NewColorable(w.Out.(*os.File))
   106  	}
   107  
   108  	if w.PartsOrder == nil {
   109  		w.PartsOrder = consoleDefaultPartsOrder()
   110  	}
   111  
   112  	var buf = consoleBufPool.Get().(*bytes.Buffer)
   113  	defer func() {
   114  		buf.Reset()
   115  		consoleBufPool.Put(buf)
   116  	}()
   117  
   118  	var evt map[string]interface{}
   119  	p = decodeIfBinaryToBytes(p)
   120  	d := json.NewDecoder(bytes.NewReader(p))
   121  	d.UseNumber()
   122  	err = d.Decode(&evt)
   123  	if err != nil {
   124  		return n, fmt.Errorf("cannot decode event: %s", err)
   125  	}
   126  
   127  	for _, p := range w.PartsOrder {
   128  		w.writePart(buf, evt, p)
   129  	}
   130  
   131  	w.writeFields(evt, buf)
   132  
   133  	if w.FormatExtra != nil {
   134  		err = w.FormatExtra(evt, buf)
   135  		if err != nil {
   136  			return n, err
   137  		}
   138  	}
   139  
   140  	err = buf.WriteByte('\n')
   141  	if err != nil {
   142  		return n, err
   143  	}
   144  
   145  	_, err = buf.WriteTo(w.Out)
   146  	return len(p), err
   147  }
   148  
   149  // writeFields appends formatted key-value pairs to buf.
   150  func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
   151  	var fields = make([]string, 0, len(evt))
   152  	for field := range evt {
   153  		var isExcluded bool
   154  		for _, excluded := range w.FieldsExclude {
   155  			if field == excluded {
   156  				isExcluded = true
   157  				break
   158  			}
   159  		}
   160  		if isExcluded {
   161  			continue
   162  		}
   163  
   164  		switch field {
   165  		case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName:
   166  			continue
   167  		}
   168  		fields = append(fields, field)
   169  	}
   170  	sort.Strings(fields)
   171  
   172  	// Write space only if something has already been written to the buffer, and if there are fields.
   173  	if buf.Len() > 0 && len(fields) > 0 {
   174  		buf.WriteByte(' ')
   175  	}
   176  
   177  	// Move the "error" field to the front
   178  	ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName })
   179  	if ei < len(fields) && fields[ei] == ErrorFieldName {
   180  		fields[ei] = ""
   181  		fields = append([]string{ErrorFieldName}, fields...)
   182  		var xfields = make([]string, 0, len(fields))
   183  		for _, field := range fields {
   184  			if field == "" { // Skip empty fields
   185  				continue
   186  			}
   187  			xfields = append(xfields, field)
   188  		}
   189  		fields = xfields
   190  	}
   191  
   192  	for i, field := range fields {
   193  		var fn Formatter
   194  		var fv Formatter
   195  
   196  		if field == ErrorFieldName {
   197  			if w.FormatErrFieldName == nil {
   198  				fn = consoleDefaultFormatErrFieldName(w.NoColor)
   199  			} else {
   200  				fn = w.FormatErrFieldName
   201  			}
   202  
   203  			if w.FormatErrFieldValue == nil {
   204  				fv = consoleDefaultFormatErrFieldValue(w.NoColor)
   205  			} else {
   206  				fv = w.FormatErrFieldValue
   207  			}
   208  		} else {
   209  			if w.FormatFieldName == nil {
   210  				fn = consoleDefaultFormatFieldName(w.NoColor)
   211  			} else {
   212  				fn = w.FormatFieldName
   213  			}
   214  
   215  			if w.FormatFieldValue == nil {
   216  				fv = consoleDefaultFormatFieldValue
   217  			} else {
   218  				fv = w.FormatFieldValue
   219  			}
   220  		}
   221  
   222  		buf.WriteString(fn(field))
   223  
   224  		switch fValue := evt[field].(type) {
   225  		case string:
   226  			if needsQuote(fValue) {
   227  				buf.WriteString(fv(strconv.Quote(fValue)))
   228  			} else {
   229  				buf.WriteString(fv(fValue))
   230  			}
   231  		case json.Number:
   232  			buf.WriteString(fv(fValue))
   233  		default:
   234  			b, err := InterfaceMarshalFunc(fValue)
   235  			if err != nil {
   236  				fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
   237  			} else {
   238  				fmt.Fprint(buf, fv(b))
   239  			}
   240  		}
   241  
   242  		if i < len(fields)-1 { // Skip space for last field
   243  			buf.WriteByte(' ')
   244  		}
   245  	}
   246  }
   247  
   248  // writePart appends a formatted part to buf.
   249  func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
   250  	var f Formatter
   251  
   252  	if w.PartsExclude != nil && len(w.PartsExclude) > 0 {
   253  		for _, exclude := range w.PartsExclude {
   254  			if exclude == p {
   255  				return
   256  			}
   257  		}
   258  	}
   259  
   260  	switch p {
   261  	case LevelFieldName:
   262  		if w.FormatLevel == nil {
   263  			f = consoleDefaultFormatLevel(w.NoColor)
   264  		} else {
   265  			f = w.FormatLevel
   266  		}
   267  	case TimestampFieldName:
   268  		if w.FormatTimestamp == nil {
   269  			f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor)
   270  		} else {
   271  			f = w.FormatTimestamp
   272  		}
   273  	case MessageFieldName:
   274  		if w.FormatMessage == nil {
   275  			f = consoleDefaultFormatMessage
   276  		} else {
   277  			f = w.FormatMessage
   278  		}
   279  	case CallerFieldName:
   280  		if w.FormatCaller == nil {
   281  			f = consoleDefaultFormatCaller(w.NoColor)
   282  		} else {
   283  			f = w.FormatCaller
   284  		}
   285  	default:
   286  		if w.FormatFieldValue == nil {
   287  			f = consoleDefaultFormatFieldValue
   288  		} else {
   289  			f = w.FormatFieldValue
   290  		}
   291  	}
   292  
   293  	var s = f(evt[p])
   294  
   295  	if len(s) > 0 {
   296  		if buf.Len() > 0 {
   297  			buf.WriteByte(' ') // Write space only if not the first part
   298  		}
   299  		buf.WriteString(s)
   300  	}
   301  }
   302  
   303  // needsQuote returns true when the string s should be quoted in output.
   304  func needsQuote(s string) bool {
   305  	for i := range s {
   306  		if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
   307  			return true
   308  		}
   309  	}
   310  	return false
   311  }
   312  
   313  // colorize returns the string s wrapped in ANSI code c, unless disabled is true.
   314  func colorize(s interface{}, c int, disabled bool) string {
   315  	if disabled {
   316  		return fmt.Sprintf("%s", s)
   317  	}
   318  	return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s)
   319  }
   320  
   321  // ----- DEFAULT FORMATTERS ---------------------------------------------------
   322  
   323  func consoleDefaultPartsOrder() []string {
   324  	return []string{
   325  		TimestampFieldName,
   326  		LevelFieldName,
   327  		CallerFieldName,
   328  		MessageFieldName,
   329  	}
   330  }
   331  
   332  func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
   333  	if timeFormat == "" {
   334  		timeFormat = consoleDefaultTimeFormat
   335  	}
   336  	return func(i interface{}) string {
   337  		t := "<nil>"
   338  		switch tt := i.(type) {
   339  		case string:
   340  			ts, err := time.Parse(TimeFieldFormat, tt)
   341  			if err != nil {
   342  				t = tt
   343  			} else {
   344  				t = ts.Local().Format(timeFormat)
   345  			}
   346  		case json.Number:
   347  			i, err := tt.Int64()
   348  			if err != nil {
   349  				t = tt.String()
   350  			} else {
   351  				var sec, nsec int64 = i, 0
   352  				switch TimeFieldFormat {
   353  				case TimeFormatUnixMs:
   354  					nsec = int64(time.Duration(i) * time.Millisecond)
   355  					sec = 0
   356  				case TimeFormatUnixMicro:
   357  					nsec = int64(time.Duration(i) * time.Microsecond)
   358  					sec = 0
   359  				}
   360  				ts := time.Unix(sec, nsec)
   361  				t = ts.Format(timeFormat)
   362  			}
   363  		}
   364  		return colorize(t, colorDarkGray, noColor)
   365  	}
   366  }
   367  
   368  func consoleDefaultFormatLevel(noColor bool) Formatter {
   369  	return func(i interface{}) string {
   370  		var l string
   371  		if ll, ok := i.(string); ok {
   372  			switch ll {
   373  			case LevelTraceValue:
   374  				l = colorize("TRC", colorMagenta, noColor)
   375  			case LevelDebugValue:
   376  				l = colorize("DBG", colorYellow, noColor)
   377  			case LevelInfoValue:
   378  				l = colorize("INF", colorGreen, noColor)
   379  			case LevelWarnValue:
   380  				l = colorize("WRN", colorRed, noColor)
   381  			case LevelErrorValue:
   382  				l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
   383  			case LevelFatalValue:
   384  				l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
   385  			case LevelPanicValue:
   386  				l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
   387  			default:
   388  				l = colorize("???", colorBold, noColor)
   389  			}
   390  		} else {
   391  			if i == nil {
   392  				l = colorize("???", colorBold, noColor)
   393  			} else {
   394  				l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
   395  			}
   396  		}
   397  		return l
   398  	}
   399  }
   400  
   401  func consoleDefaultFormatCaller(noColor bool) Formatter {
   402  	return func(i interface{}) string {
   403  		var c string
   404  		if cc, ok := i.(string); ok {
   405  			c = cc
   406  		}
   407  		if len(c) > 0 {
   408  			if cwd, err := os.Getwd(); err == nil {
   409  				if rel, err := filepath.Rel(cwd, c); err == nil {
   410  					c = rel
   411  				}
   412  			}
   413  			c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor)
   414  		}
   415  		return c
   416  	}
   417  }
   418  
   419  func consoleDefaultFormatMessage(i interface{}) string {
   420  	if i == nil {
   421  		return ""
   422  	}
   423  	return fmt.Sprintf("%s", i)
   424  }
   425  
   426  func consoleDefaultFormatFieldName(noColor bool) Formatter {
   427  	return func(i interface{}) string {
   428  		return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
   429  	}
   430  }
   431  
   432  func consoleDefaultFormatFieldValue(i interface{}) string {
   433  	return fmt.Sprintf("%s", i)
   434  }
   435  
   436  func consoleDefaultFormatErrFieldName(noColor bool) Formatter {
   437  	return func(i interface{}) string {
   438  		return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
   439  	}
   440  }
   441  
   442  func consoleDefaultFormatErrFieldValue(noColor bool) Formatter {
   443  	return func(i interface{}) string {
   444  		return colorize(fmt.Sprintf("%s", i), colorRed, noColor)
   445  	}
   446  }
   447  

View as plain text