blob: abf5cf1d229a0191523b21afdd750f3ef8d80870 [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// Copyright 2019 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package traceutil implements tracing utilities using "context".
16package traceutil
17
18import (
19 "context"
20 "fmt"
21 "math/rand"
22 "strings"
23 "time"
24
25 "go.uber.org/zap"
26)
27
28// TraceKey is used as a key of context for Trace.
29type TraceKey struct{}
30
31// StartTimeKey is used as a key of context for start time of operation.
32type StartTimeKey struct{}
33
34// Field is a kv pair to record additional details of the trace.
35type Field struct {
36 Key string
37 Value any
38}
39
40func (f *Field) format() string {
41 return fmt.Sprintf("%s:%v; ", f.Key, f.Value)
42}
43
44func writeFields(fields []Field) string {
45 if len(fields) == 0 {
46 return ""
47 }
48 var buf strings.Builder
49 buf.WriteString("{")
50 for _, f := range fields {
51 buf.WriteString(f.format())
52 }
53 buf.WriteString("}")
54 return buf.String()
55}
56
57type Trace struct {
58 operation string
59 lg *zap.Logger
60 fields []Field
61 startTime time.Time
62 steps []step
63 stepDisabled bool
64 isEmpty bool
65}
66
67type step struct {
68 time time.Time
69 msg string
70 fields []Field
71 isSubTraceStart bool
72 isSubTraceEnd bool
73}
74
75func New(op string, lg *zap.Logger, fields ...Field) *Trace {
76 return &Trace{operation: op, lg: lg, startTime: time.Now(), fields: fields}
77}
78
79// TODO returns a non-nil, empty Trace
80func TODO() *Trace {
81 return &Trace{isEmpty: true}
82}
83
84func Get(ctx context.Context) *Trace {
85 if trace, ok := ctx.Value(TraceKey{}).(*Trace); ok && trace != nil {
86 return trace
87 }
88 return TODO()
89}
90
91func (t *Trace) GetStartTime() time.Time {
92 return t.startTime
93}
94
95func (t *Trace) SetStartTime(time time.Time) {
96 t.startTime = time
97}
98
99func (t *Trace) InsertStep(at int, time time.Time, msg string, fields ...Field) {
100 newStep := step{time: time, msg: msg, fields: fields}
101 if at < len(t.steps) {
102 t.steps = append(t.steps[:at+1], t.steps[at:]...)
103 t.steps[at] = newStep
104 } else {
105 t.steps = append(t.steps, newStep)
106 }
107}
108
109// StartSubTrace adds step to trace as a start sign of sublevel trace
110// All steps in the subtrace will log out the input fields of this function
111func (t *Trace) StartSubTrace(fields ...Field) {
112 t.steps = append(t.steps, step{fields: fields, isSubTraceStart: true})
113}
114
115// StopSubTrace adds step to trace as a end sign of sublevel trace
116// All steps in the subtrace will log out the input fields of this function
117func (t *Trace) StopSubTrace(fields ...Field) {
118 t.steps = append(t.steps, step{fields: fields, isSubTraceEnd: true})
119}
120
121// Step adds step to trace
122func (t *Trace) Step(msg string, fields ...Field) {
123 if !t.stepDisabled {
124 t.steps = append(t.steps, step{time: time.Now(), msg: msg, fields: fields})
125 }
126}
127
128// StepWithFunction will measure the input function as a single step
129func (t *Trace) StepWithFunction(f func(), msg string, fields ...Field) {
130 t.disableStep()
131 f()
132 t.enableStep()
133 t.Step(msg, fields...)
134}
135
136func (t *Trace) AddField(fields ...Field) {
137 for _, f := range fields {
138 if !t.updateFieldIfExist(f) {
139 t.fields = append(t.fields, f)
140 }
141 }
142}
143
144func (t *Trace) IsEmpty() bool {
145 return t.isEmpty
146}
147
148// Log dumps all steps in the Trace
149func (t *Trace) Log() {
150 t.LogWithStepThreshold(0)
151}
152
153// LogIfLong dumps logs if the duration is longer than threshold
154func (t *Trace) LogIfLong(threshold time.Duration) {
155 if time.Since(t.startTime) > threshold {
156 stepThreshold := threshold / time.Duration(len(t.steps)+1)
157 t.LogWithStepThreshold(stepThreshold)
158 }
159}
160
161// LogAllStepsIfLong dumps all logs if the duration is longer than threshold
162func (t *Trace) LogAllStepsIfLong(threshold time.Duration) {
163 if time.Since(t.startTime) > threshold {
164 t.LogWithStepThreshold(0)
165 }
166}
167
168// LogWithStepThreshold only dumps step whose duration is longer than step threshold
169func (t *Trace) LogWithStepThreshold(threshold time.Duration) {
170 msg, fs := t.logInfo(threshold)
171 if t.lg != nil {
172 t.lg.Info(msg, fs...)
173 }
174}
175
176func (t *Trace) logInfo(threshold time.Duration) (string, []zap.Field) {
177 endTime := time.Now()
178 totalDuration := endTime.Sub(t.startTime)
179 traceNum := rand.Int31()
180 msg := fmt.Sprintf("trace[%d] %s", traceNum, t.operation)
181
182 var steps []string
183 lastStepTime := t.startTime
184 for i := 0; i < len(t.steps); i++ {
185 tstep := t.steps[i]
186 // add subtrace common fields which defined at the beginning to each sub-steps
187 if tstep.isSubTraceStart {
188 for j := i + 1; j < len(t.steps) && !t.steps[j].isSubTraceEnd; j++ {
189 t.steps[j].fields = append(tstep.fields, t.steps[j].fields...)
190 }
191 continue
192 }
193 // add subtrace common fields which defined at the end to each sub-steps
194 if tstep.isSubTraceEnd {
195 for j := i - 1; j >= 0 && !t.steps[j].isSubTraceStart; j-- {
196 t.steps[j].fields = append(tstep.fields, t.steps[j].fields...)
197 }
198 continue
199 }
200 }
201 for i := 0; i < len(t.steps); i++ {
202 tstep := t.steps[i]
203 if tstep.isSubTraceStart || tstep.isSubTraceEnd {
204 continue
205 }
206 stepDuration := tstep.time.Sub(lastStepTime)
207 if stepDuration > threshold {
208 steps = append(steps, fmt.Sprintf("trace[%d] '%v' %s (duration: %v)",
209 traceNum, tstep.msg, writeFields(tstep.fields), stepDuration))
210 }
211 lastStepTime = tstep.time
212 }
213
214 fs := []zap.Field{
215 zap.String("detail", writeFields(t.fields)),
216 zap.Duration("duration", totalDuration),
217 zap.Time("start", t.startTime),
218 zap.Time("end", endTime),
219 zap.Strings("steps", steps),
220 zap.Int("step_count", len(steps)),
221 }
222 return msg, fs
223}
224
225func (t *Trace) updateFieldIfExist(f Field) bool {
226 for i, v := range t.fields {
227 if v.Key == f.Key {
228 t.fields[i].Value = f.Value
229 return true
230 }
231 }
232 return false
233}
234
235// disableStep sets the flag to prevent the trace from adding steps
236func (t *Trace) disableStep() {
237 t.stepDisabled = true
238}
239
240// enableStep re-enable the trace to add steps
241func (t *Trace) enableStep() {
242 t.stepDisabled = false
243}