github.com/wiselike/revel-cmd@v1.2.1/logger/terminal_format.go (about)

     1  package logger
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  	"strconv"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  const (
    14  	timeFormat          = "2006-01-02T15:04:05-0700"
    15  	termTimeFormat      = "2006/01/02 15:04:05"
    16  	termSmallTimeFormat = "15:04:05"
    17  	floatFormat         = 'f'
    18  	errorKey            = "REVEL_ERROR"
    19  )
    20  
    21  var levelString = map[LogLevel]string{
    22  	LvlDebug: "DEBUG",
    23  	LvlInfo:  "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT",
    24  }
    25  
    26  // Outputs to the terminal in a format like below
    27  // INFO  09:11:32 server-engine.go:169: Request Stats.
    28  func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
    29  	dateFormat := termTimeFormat
    30  	if smallDate {
    31  		dateFormat = termSmallTimeFormat
    32  	}
    33  	return FormatFunc(func(r *Record) []byte {
    34  		// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
    35  		color := 0
    36  		switch r.Level {
    37  		case LvlCrit:
    38  			// Magenta
    39  			color = 35
    40  		case LvlError:
    41  			// Red
    42  			color = 31
    43  		case LvlWarn:
    44  			// Yellow
    45  			color = 33
    46  		case LvlInfo:
    47  			// Green
    48  			color = 32
    49  		case LvlDebug:
    50  			// Cyan
    51  			color = 36
    52  		}
    53  
    54  		b := &bytes.Buffer{}
    55  		caller, _ := r.Context["caller"].(string)
    56  		module, _ := r.Context["module"].(string)
    57  		if !noColor && color > 0 {
    58  			if len(module) > 0 {
    59  				fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
    60  			} else {
    61  				fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message)
    62  			}
    63  		} else {
    64  			fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
    65  		}
    66  
    67  		i := 0
    68  		for k, v := range r.Context {
    69  			if i != 0 {
    70  				b.WriteByte(' ')
    71  			}
    72  			i++
    73  			if k == "module" || k == "caller" {
    74  				continue
    75  			}
    76  
    77  			v := formatLogfmtValue(v)
    78  
    79  			// TODO: we should probably check that all of your key bytes aren't invalid
    80  			if !noColor && color > 0 {
    81  				fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
    82  			} else {
    83  				b.WriteString(k)
    84  				b.WriteByte('=')
    85  				b.WriteString(v)
    86  			}
    87  		}
    88  
    89  		b.WriteByte('\n')
    90  
    91  		return b.Bytes()
    92  	})
    93  }
    94  
    95  // formatValue formats a value for serialization.
    96  func formatLogfmtValue(value interface{}) string {
    97  	if value == nil {
    98  		return "nil"
    99  	}
   100  
   101  	if t, ok := value.(time.Time); ok {
   102  		// Performance optimization: No need for escaping since the provided
   103  		// timeFormat doesn't have any escape characters, and escaping is
   104  		// expensive.
   105  		return t.Format(termTimeFormat)
   106  	}
   107  	value = formatShared(value)
   108  	switch v := value.(type) {
   109  	case bool:
   110  		return strconv.FormatBool(v)
   111  	case float32:
   112  		return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
   113  	case float64:
   114  		return strconv.FormatFloat(v, floatFormat, 7, 64)
   115  	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   116  		return fmt.Sprintf("%d", value)
   117  	case string:
   118  		return escapeString(v)
   119  	default:
   120  		return escapeString(fmt.Sprintf("%+v", value))
   121  	}
   122  }
   123  
   124  // Format the value in json format.
   125  func formatShared(value interface{}) (result interface{}) {
   126  	defer func() {
   127  		if err := recover(); err != nil {
   128  			if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
   129  				result = "nil"
   130  			} else {
   131  				panic(err)
   132  			}
   133  		}
   134  	}()
   135  
   136  	switch v := value.(type) {
   137  	case time.Time:
   138  		return v.Format(timeFormat)
   139  
   140  	case error:
   141  		return v.Error()
   142  
   143  	case fmt.Stringer:
   144  		return v.String()
   145  
   146  	default:
   147  		return v
   148  	}
   149  }
   150  
   151  // A reusuable buffer for outputting data.
   152  var stringBufPool = sync.Pool{
   153  	New: func() interface{} { return new(bytes.Buffer) },
   154  }
   155  
   156  // Escape the string when needed.
   157  func escapeString(s string) string {
   158  	needsQuotes := false
   159  	needsEscape := false
   160  	for _, r := range s {
   161  		if r <= ' ' || r == '=' || r == '"' {
   162  			needsQuotes = true
   163  		}
   164  		if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
   165  			needsEscape = true
   166  		}
   167  	}
   168  	if !needsEscape && !needsQuotes {
   169  		return s
   170  	}
   171  	e := stringBufPool.Get().(*bytes.Buffer)
   172  	e.WriteByte('"')
   173  	for _, r := range s {
   174  		switch r {
   175  		case '\\', '"':
   176  			e.WriteByte('\\')
   177  			e.WriteByte(byte(r))
   178  		case '\n':
   179  			e.WriteString("\\n")
   180  		case '\r':
   181  			e.WriteString("\\r")
   182  		case '\t':
   183  			e.WriteString("\\t")
   184  		default:
   185  			e.WriteRune(r)
   186  		}
   187  	}
   188  	e.WriteByte('"')
   189  	var ret string
   190  	if needsQuotes {
   191  		ret = e.String()
   192  	} else {
   193  		ret = string(e.Bytes()[1 : e.Len()-1])
   194  	}
   195  	e.Reset()
   196  	stringBufPool.Put(e)
   197  	return ret
   198  }
   199  
   200  // JSONFormatEx formats log records as JSON objects. If pretty is true,
   201  // records will be pretty-printed. If lineSeparated is true, records
   202  // will be logged with a new line between each record.
   203  func JSONFormatEx(pretty, lineSeparated bool) LogFormat {
   204  	jsonMarshal := json.Marshal
   205  	if pretty {
   206  		jsonMarshal = func(v interface{}) ([]byte, error) {
   207  			return json.MarshalIndent(v, "", "    ")
   208  		}
   209  	}
   210  
   211  	return FormatFunc(func(r *Record) []byte {
   212  		props := make(map[string]interface{})
   213  
   214  		props["t"] = r.Time
   215  		props["lvl"] = levelString[r.Level]
   216  		props["msg"] = r.Message
   217  		for k, v := range r.Context {
   218  			props[k] = formatJSONValue(v)
   219  		}
   220  
   221  		b, err := jsonMarshal(props)
   222  		if err != nil {
   223  			b, _ = jsonMarshal(map[string]string{
   224  				errorKey: err.Error(),
   225  			})
   226  			return b
   227  		}
   228  
   229  		if lineSeparated {
   230  			b = append(b, '\n')
   231  		}
   232  
   233  		return b
   234  	})
   235  }
   236  
   237  func formatJSONValue(value interface{}) interface{} {
   238  	value = formatShared(value)
   239  	switch value.(type) {
   240  	case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
   241  		return value
   242  	default:
   243  		return fmt.Sprintf("%+v", value)
   244  	}
   245  }