1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package traceutil
17
18 import (
19 "bytes"
20 "context"
21 "fmt"
22 "math/rand"
23 "time"
24
25 "go.uber.org/zap"
26 )
27
28 const (
29 TraceKey = "trace"
30 StartTimeKey = "startTime"
31 )
32
33
34 type Field struct {
35 Key string
36 Value interface{}
37 }
38
39 func (f *Field) format() string {
40 return fmt.Sprintf("%s:%v; ", f.Key, f.Value)
41 }
42
43 func writeFields(fields []Field) string {
44 if len(fields) == 0 {
45 return ""
46 }
47 var buf bytes.Buffer
48 buf.WriteString("{")
49 for _, f := range fields {
50 buf.WriteString(f.format())
51 }
52 buf.WriteString("}")
53 return buf.String()
54 }
55
56 type Trace struct {
57 operation string
58 lg *zap.Logger
59 fields []Field
60 startTime time.Time
61 steps []step
62 stepDisabled bool
63 isEmpty bool
64 }
65
66 type step struct {
67 time time.Time
68 msg string
69 fields []Field
70 isSubTraceStart bool
71 isSubTraceEnd bool
72 }
73
74 func New(op string, lg *zap.Logger, fields ...Field) *Trace {
75 return &Trace{operation: op, lg: lg, startTime: time.Now(), fields: fields}
76 }
77
78
79 func TODO() *Trace {
80 return &Trace{isEmpty: true}
81 }
82
83 func Get(ctx context.Context) *Trace {
84 if trace, ok := ctx.Value(TraceKey).(*Trace); ok && trace != nil {
85 return trace
86 }
87 return TODO()
88 }
89
90 func (t *Trace) GetStartTime() time.Time {
91 return t.startTime
92 }
93
94 func (t *Trace) SetStartTime(time time.Time) {
95 t.startTime = time
96 }
97
98 func (t *Trace) InsertStep(at int, time time.Time, msg string, fields ...Field) {
99 newStep := step{time: time, msg: msg, fields: fields}
100 if at < len(t.steps) {
101 t.steps = append(t.steps[:at+1], t.steps[at:]...)
102 t.steps[at] = newStep
103 } else {
104 t.steps = append(t.steps, newStep)
105 }
106 }
107
108
109
110 func (t *Trace) StartSubTrace(fields ...Field) {
111 t.steps = append(t.steps, step{fields: fields, isSubTraceStart: true})
112 }
113
114
115
116 func (t *Trace) StopSubTrace(fields ...Field) {
117 t.steps = append(t.steps, step{fields: fields, isSubTraceEnd: true})
118 }
119
120
121 func (t *Trace) Step(msg string, fields ...Field) {
122 if !t.stepDisabled {
123 t.steps = append(t.steps, step{time: time.Now(), msg: msg, fields: fields})
124 }
125 }
126
127
128 func (t *Trace) StepWithFunction(f func(), msg string, fields ...Field) {
129 t.disableStep()
130 f()
131 t.enableStep()
132 t.Step(msg, fields...)
133 }
134
135 func (t *Trace) AddField(fields ...Field) {
136 for _, f := range fields {
137 if !t.updateFieldIfExist(f) {
138 t.fields = append(t.fields, f)
139 }
140 }
141 }
142
143 func (t *Trace) IsEmpty() bool {
144 return t.isEmpty
145 }
146
147
148 func (t *Trace) Log() {
149 t.LogWithStepThreshold(0)
150 }
151
152
153 func (t *Trace) LogIfLong(threshold time.Duration) {
154 if time.Since(t.startTime) > threshold {
155 stepThreshold := threshold / time.Duration(len(t.steps)+1)
156 t.LogWithStepThreshold(stepThreshold)
157 }
158 }
159
160
161 func (t *Trace) LogAllStepsIfLong(threshold time.Duration) {
162 if time.Since(t.startTime) > threshold {
163 t.LogWithStepThreshold(0)
164 }
165 }
166
167
168 func (t *Trace) LogWithStepThreshold(threshold time.Duration) {
169 msg, fs := t.logInfo(threshold)
170 if t.lg != nil {
171 t.lg.Info(msg, fs...)
172 }
173 }
174
175 func (t *Trace) logInfo(threshold time.Duration) (string, []zap.Field) {
176 endTime := time.Now()
177 totalDuration := endTime.Sub(t.startTime)
178 traceNum := rand.Int31()
179 msg := fmt.Sprintf("trace[%d] %s", traceNum, t.operation)
180
181 var steps []string
182 lastStepTime := t.startTime
183 for i := 0; i < len(t.steps); i++ {
184 step := t.steps[i]
185
186 if step.isSubTraceStart {
187 for j := i + 1; j < len(t.steps) && !t.steps[j].isSubTraceEnd; j++ {
188 t.steps[j].fields = append(step.fields, t.steps[j].fields...)
189 }
190 continue
191 }
192
193 if step.isSubTraceEnd {
194 for j := i - 1; j >= 0 && !t.steps[j].isSubTraceStart; j-- {
195 t.steps[j].fields = append(step.fields, t.steps[j].fields...)
196 }
197 continue
198 }
199 }
200 for i := 0; i < len(t.steps); i++ {
201 step := t.steps[i]
202 if step.isSubTraceStart || step.isSubTraceEnd {
203 continue
204 }
205 stepDuration := step.time.Sub(lastStepTime)
206 if stepDuration > threshold {
207 steps = append(steps, fmt.Sprintf("trace[%d] '%v' %s (duration: %v)",
208 traceNum, step.msg, writeFields(step.fields), stepDuration))
209 }
210 lastStepTime = step.time
211 }
212
213 fs := []zap.Field{zap.String("detail", writeFields(t.fields)),
214 zap.Duration("duration", totalDuration),
215 zap.Time("start", t.startTime),
216 zap.Time("end", endTime),
217 zap.Strings("steps", steps),
218 zap.Int("step_count", len(steps))}
219 return msg, fs
220 }
221
222 func (t *Trace) updateFieldIfExist(f Field) bool {
223 for i, v := range t.fields {
224 if v.Key == f.Key {
225 t.fields[i].Value = f.Value
226 return true
227 }
228 }
229 return false
230 }
231
232
233 func (t *Trace) disableStep() {
234 t.stepDisabled = true
235 }
236
237
238 func (t *Trace) enableStep() {
239 t.stepDisabled = false
240 }
241
View as plain text