-
Notifications
You must be signed in to change notification settings - Fork 1
/
log_message.go
150 lines (134 loc) · 3.52 KB
/
log_message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package golog
import (
"bytes"
"fmt"
"os"
"path"
"runtime"
"strconv"
"strings"
"time"
)
type LogMessage struct {
Level int
Nanoseconds time.Time
Message string
// A map from the type of metadata to the metadata, if present.
// By convention, fields in this map will be entirely lowercase and
// single word.
Metadata map[string]string
}
// TODO(awreece) comment this
// Skip 0 refers to the function calling this function.
// Walks up the stack skip frames and returns the metatdata for that frame.
type MetadataFunc func(skip int) map[string]string
var NoLocation MetadataFunc = func(skip int) map[string]string {
// TODO(awreece) Add timestamp?
return make(map[string]string)
}
type LocationFlag int
const (
None LocationFlag = 1 << iota
Package
Function
File
Line
Hostname
DefaultMetadata = File | Line
All = Package | Function | File | Line | Hostname
requiresPC = Package | Function | File | Line
)
// Returns a function the computes the specified fields of metadata for the log
// message.
//
// flags is the set of locations to add to the metadata. For example,
// MakeMetadataFunc(File | Line | Hostname)
func MakeMetadataFunc(flags LocationFlag) MetadataFunc {
return func(skip int) map[string]string {
ret := NoLocation(skip + 1)
// TODO(awreece) Refactor.
if flags&requiresPC > 0 {
// Don't get the pc unless we have to.
if pc, file, line, ok := runtime.Caller(skip + 1); ok {
// Don't get FuncForPC unless we have to.
if flags&Package > 0 || flags&Function > 0 {
// TODO(awreece) Make sure this is
// compiler agnostic.
funcParts := strings.SplitN(
runtime.FuncForPC(pc).Name(),
".", 2)
if flags&Package > 0 {
ret["package"] = funcParts[0]
}
if flags&Function > 0 {
ret["function"] = funcParts[1]
}
}
if flags&File > 0 {
ret["file"] = path.Base(file)
}
if flags&Line > 0 {
ret["line"] = strconv.Itoa(line)
}
}
}
if flags&Hostname > 0 {
if host, err := os.Hostname(); err == nil {
ret["hostname"] = host
}
}
return ret
}
}
// Render the formatted metadata to the buffer. If all present, format is
// "{time} {pack}.{func}/{file}:{line}". If some fields omitted, intelligently
// delimits the remaining fields.
func renderMetadata(buf *bytes.Buffer, m *LogMessage) {
if m == nil {
// TODO(awreece) Panic here?
return
}
buf.WriteString(m.Nanoseconds.Format(" 15:04:05.000000"))
packName, packPresent := m.Metadata["package"]
file, filePresent := m.Metadata["file"]
funcName, funcPresent := m.Metadata["function"]
line, linePresent := m.Metadata["line"]
if packPresent || filePresent || funcPresent || linePresent {
buf.WriteString(" ")
}
// TODO(awreece) This logic is terrifying.
if packPresent {
buf.WriteString(packName)
}
if funcPresent {
if packPresent {
buf.WriteString(".")
}
buf.WriteString(funcName)
}
if (packPresent || funcPresent) && (filePresent || linePresent) {
buf.WriteString("/")
}
if filePresent {
buf.WriteString(file)
}
if linePresent {
if filePresent {
buf.WriteString(":")
}
buf.WriteString(line)
}
}
// Format the message as a string, optionally inserting a newline.
// Format is: "L{level} {time} {pack}.{func}/{file}:{line}] {message}"
func formatLogMessage(m *LogMessage, insertNewline bool) string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("L%d", m.Level))
renderMetadata(&buf, m)
buf.WriteString("] ")
buf.WriteString(m.Message)
if insertNewline {
buf.WriteString("\n")
}
return buf.String()
}