github.com/jd-ly/cmd@v1.0.10/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 ( 22 levelString = map[LogLevel]string{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 var 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 == false && 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 == false && 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 == false && needsQuotes == false { 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 }