Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / logger / terminal_format.go
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 }