Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

Commit 0c3154a

Browse files
authored
Merge pull request #108 from opentracing/bhs/spanlog
Introduce key-value Span logging as an RFC
2 parents 449a42d + d3768c5 commit 0c3154a

File tree

9 files changed

+625
-75
lines changed

9 files changed

+625
-75
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ happily rely on it for `Span` propagation. To start a new (blocking child)
5050
...
5151
span, ctx := opentracing.StartSpanFromContext(ctx, "operation_name")
5252
defer span.Finish()
53-
span.LogEvent("xyz_called")
53+
span.LogFields(
54+
log.String("event", "soft error"),
55+
log.String("type", "cache timeout"),
56+
log.Int("waited.millis", 1500))
5457
...
5558
}
5659
```
@@ -65,7 +68,6 @@ reference.
6568
...
6669
sp := opentracing.StartSpan("operation_name")
6770
defer sp.Finish()
68-
sp.LogEvent("xyz_called")
6971
...
7072
}
7173
```
@@ -79,7 +81,6 @@ reference.
7981
"operation_name",
8082
opentracing.ChildOf(parentSpan.Context()))
8183
defer sp.Finish()
82-
sp.LogEvent("xyz_called")
8384
...
8485
}
8586
```

log/field.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package log
2+
3+
import "math"
4+
5+
type fieldType int
6+
7+
const (
8+
stringType fieldType = iota
9+
boolType
10+
intType
11+
int32Type
12+
uint32Type
13+
int64Type
14+
uint64Type
15+
float32Type
16+
float64Type
17+
errorType
18+
objectType
19+
lazyLoggerType
20+
)
21+
22+
// Field instances are constructed via LogBool, LogString, and so on.
23+
// Tracing implementations may then handle them via the Field.Process
24+
// method.
25+
//
26+
// "heavily influenced by" (i.e., partially stolen from)
27+
// https://github.com/uber-go/zap
28+
type Field struct {
29+
key string
30+
fieldType fieldType
31+
numericVal int64
32+
stringVal string
33+
interfaceVal interface{}
34+
}
35+
36+
// String adds a string-valued key:value pair to a Span.LogFields() record
37+
func String(key, val string) Field {
38+
return Field{
39+
key: key,
40+
fieldType: stringType,
41+
stringVal: val,
42+
}
43+
}
44+
45+
// Bool adds a bool-valued key:value pair to a Span.LogFields() record
46+
func Bool(key string, val bool) Field {
47+
var numericVal int64
48+
if val {
49+
numericVal = 1
50+
}
51+
return Field{
52+
key: key,
53+
fieldType: boolType,
54+
numericVal: numericVal,
55+
}
56+
}
57+
58+
// Int adds an int-valued key:value pair to a Span.LogFields() record
59+
func Int(key string, val int) Field {
60+
return Field{
61+
key: key,
62+
fieldType: intType,
63+
numericVal: int64(val),
64+
}
65+
}
66+
67+
// Int32 adds an int32-valued key:value pair to a Span.LogFields() record
68+
func Int32(key string, val int32) Field {
69+
return Field{
70+
key: key,
71+
fieldType: int32Type,
72+
numericVal: int64(val),
73+
}
74+
}
75+
76+
// Int64 adds an int64-valued key:value pair to a Span.LogFields() record
77+
func Int64(key string, val int64) Field {
78+
return Field{
79+
key: key,
80+
fieldType: int64Type,
81+
numericVal: val,
82+
}
83+
}
84+
85+
// Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record
86+
func Uint32(key string, val uint32) Field {
87+
return Field{
88+
key: key,
89+
fieldType: uint32Type,
90+
numericVal: int64(val),
91+
}
92+
}
93+
94+
// Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record
95+
func Uint64(key string, val uint64) Field {
96+
return Field{
97+
key: key,
98+
fieldType: uint64Type,
99+
numericVal: int64(val),
100+
}
101+
}
102+
103+
// Float32 adds a float32-valued key:value pair to a Span.LogFields() record
104+
func Float32(key string, val float32) Field {
105+
return Field{
106+
key: key,
107+
fieldType: float32Type,
108+
numericVal: int64(math.Float32bits(val)),
109+
}
110+
}
111+
112+
// Float64 adds a float64-valued key:value pair to a Span.LogFields() record
113+
func Float64(key string, val float64) Field {
114+
return Field{
115+
key: key,
116+
fieldType: float64Type,
117+
numericVal: int64(math.Float64bits(val)),
118+
}
119+
}
120+
121+
// Error adds an error with the key "error" to a Span.LogFields() record
122+
func Error(err error) Field {
123+
return Field{
124+
key: "error",
125+
fieldType: errorType,
126+
interfaceVal: err,
127+
}
128+
}
129+
130+
// Object adds an object-valued key:value pair to a Span.LogFields() record
131+
func Object(key string, obj interface{}) Field {
132+
return Field{
133+
key: key,
134+
fieldType: objectType,
135+
interfaceVal: obj,
136+
}
137+
}
138+
139+
// LazyLogger allows for user-defined, late-bound logging of arbitrary data
140+
type LazyLogger func(fv Encoder)
141+
142+
// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
143+
// implementation will call the LazyLogger function at an indefinite time in
144+
// the future (after Lazy() returns).
145+
func Lazy(ll LazyLogger) Field {
146+
return Field{
147+
fieldType: lazyLoggerType,
148+
interfaceVal: ll,
149+
}
150+
}
151+
152+
// Encoder allows access to the contents of a Field (via a call to
153+
// Field.Marshal).
154+
//
155+
// Tracer implementations typically provide an implementation of Encoder;
156+
// OpenTracing callers typically do not need to concern themselves with it.
157+
type Encoder interface {
158+
EmitString(key, value string)
159+
EmitBool(key string, value bool)
160+
EmitInt(key string, value int)
161+
EmitInt32(key string, value int32)
162+
EmitInt64(key string, value int64)
163+
EmitUint32(key string, value uint32)
164+
EmitUint64(key string, value uint64)
165+
EmitFloat32(key string, value float32)
166+
EmitFloat64(key string, value float64)
167+
EmitObject(key string, value interface{})
168+
EmitLazyLogger(value LazyLogger)
169+
}
170+
171+
// Marshal passes a Field instance through to the appropriate
172+
// field-type-specific method of an Encoder.
173+
func (lf Field) Marshal(visitor Encoder) {
174+
switch lf.fieldType {
175+
case stringType:
176+
visitor.EmitString(lf.key, lf.stringVal)
177+
case boolType:
178+
visitor.EmitBool(lf.key, lf.numericVal != 0)
179+
case intType:
180+
visitor.EmitInt(lf.key, int(lf.numericVal))
181+
case int32Type:
182+
visitor.EmitInt32(lf.key, int32(lf.numericVal))
183+
case int64Type:
184+
visitor.EmitInt64(lf.key, int64(lf.numericVal))
185+
case uint32Type:
186+
visitor.EmitUint32(lf.key, uint32(lf.numericVal))
187+
case uint64Type:
188+
visitor.EmitUint64(lf.key, uint64(lf.numericVal))
189+
case float32Type:
190+
visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
191+
case float64Type:
192+
visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
193+
case errorType:
194+
visitor.EmitString(lf.key, lf.interfaceVal.(error).Error())
195+
case objectType:
196+
visitor.EmitObject(lf.key, lf.interfaceVal)
197+
case lazyLoggerType:
198+
visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
199+
}
200+
}

log/util.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package log
2+
3+
import "fmt"
4+
5+
// InterleavedKVToFields converts keyValues a la Span.LogKV() to a Field slice
6+
// a la Span.LogFields().
7+
func InterleavedKVToFields(keyValues ...interface{}) ([]Field, error) {
8+
if len(keyValues)%2 != 0 {
9+
return nil, fmt.Errorf("non-even keyValues len: %d", len(keyValues))
10+
}
11+
fields := make([]Field, len(keyValues)/2)
12+
for i := 0; i*2 < len(keyValues); i++ {
13+
key, ok := keyValues[i*2].(string)
14+
if !ok {
15+
return nil, fmt.Errorf(
16+
"non-string key (pair #%d): %T",
17+
i, keyValues[i*2])
18+
}
19+
switch typedVal := keyValues[i*2+1].(type) {
20+
case bool:
21+
fields[i] = Bool(key, typedVal)
22+
case string:
23+
fields[i] = String(key, typedVal)
24+
case int:
25+
fields[i] = Int(key, typedVal)
26+
case int8:
27+
fields[i] = Int32(key, int32(typedVal))
28+
case int16:
29+
fields[i] = Int32(key, int32(typedVal))
30+
case int32:
31+
fields[i] = Int32(key, typedVal)
32+
case int64:
33+
fields[i] = Int64(key, typedVal)
34+
case uint:
35+
fields[i] = Uint64(key, uint64(typedVal))
36+
case uint64:
37+
fields[i] = Uint64(key, typedVal)
38+
case uint8:
39+
fields[i] = Uint32(key, uint32(typedVal))
40+
case uint16:
41+
fields[i] = Uint32(key, uint32(typedVal))
42+
case uint32:
43+
fields[i] = Uint32(key, typedVal)
44+
case float32:
45+
fields[i] = Float32(key, typedVal)
46+
case float64:
47+
fields[i] = Float64(key, typedVal)
48+
default:
49+
// When in doubt, coerce to a string
50+
fields[i] = String(key, fmt.Sprint(typedVal))
51+
}
52+
}
53+
return fields, nil
54+
}

mocktracer/mocklogrecord.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package mocktracer
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"time"
7+
8+
"github.com/opentracing/opentracing-go/log"
9+
)
10+
11+
// MockLogRecord represents data logged to a Span via Span.LogFields or
12+
// Span.LogKV.
13+
type MockLogRecord struct {
14+
Timestamp time.Time
15+
Fields []MockKeyValue
16+
}
17+
18+
// MockKeyValue represents a single key:value pair.
19+
type MockKeyValue struct {
20+
Key string
21+
22+
// All MockLogRecord values are coerced to strings via fmt.Sprint(), though
23+
// we retain their type separately.
24+
ValueKind reflect.Kind
25+
ValueString string
26+
}
27+
28+
// EmitString belongs to the log.Encoder interface
29+
func (m *MockKeyValue) EmitString(key, value string) {
30+
m.Key = key
31+
m.ValueKind = reflect.TypeOf(value).Kind()
32+
m.ValueString = fmt.Sprint(value)
33+
}
34+
35+
// EmitBool belongs to the log.Encoder interface
36+
func (m *MockKeyValue) EmitBool(key string, value bool) {
37+
m.Key = key
38+
m.ValueKind = reflect.TypeOf(value).Kind()
39+
m.ValueString = fmt.Sprint(value)
40+
}
41+
42+
// EmitInt belongs to the log.Encoder interface
43+
func (m *MockKeyValue) EmitInt(key string, value int) {
44+
m.Key = key
45+
m.ValueKind = reflect.TypeOf(value).Kind()
46+
m.ValueString = fmt.Sprint(value)
47+
}
48+
49+
// EmitInt32 belongs to the log.Encoder interface
50+
func (m *MockKeyValue) EmitInt32(key string, value int32) {
51+
m.Key = key
52+
m.ValueKind = reflect.TypeOf(value).Kind()
53+
m.ValueString = fmt.Sprint(value)
54+
}
55+
56+
// EmitInt64 belongs to the log.Encoder interface
57+
func (m *MockKeyValue) EmitInt64(key string, value int64) {
58+
m.Key = key
59+
m.ValueKind = reflect.TypeOf(value).Kind()
60+
m.ValueString = fmt.Sprint(value)
61+
}
62+
63+
// EmitUint32 belongs to the log.Encoder interface
64+
func (m *MockKeyValue) EmitUint32(key string, value uint32) {
65+
m.Key = key
66+
m.ValueKind = reflect.TypeOf(value).Kind()
67+
m.ValueString = fmt.Sprint(value)
68+
}
69+
70+
// EmitUint64 belongs to the log.Encoder interface
71+
func (m *MockKeyValue) EmitUint64(key string, value uint64) {
72+
m.Key = key
73+
m.ValueKind = reflect.TypeOf(value).Kind()
74+
m.ValueString = fmt.Sprint(value)
75+
}
76+
77+
// EmitFloat32 belongs to the log.Encoder interface
78+
func (m *MockKeyValue) EmitFloat32(key string, value float32) {
79+
m.Key = key
80+
m.ValueKind = reflect.TypeOf(value).Kind()
81+
m.ValueString = fmt.Sprint(value)
82+
}
83+
84+
// EmitFloat64 belongs to the log.Encoder interface
85+
func (m *MockKeyValue) EmitFloat64(key string, value float64) {
86+
m.Key = key
87+
m.ValueKind = reflect.TypeOf(value).Kind()
88+
m.ValueString = fmt.Sprint(value)
89+
}
90+
91+
// EmitObject belongs to the log.Encoder interface
92+
func (m *MockKeyValue) EmitObject(key string, value interface{}) {
93+
m.Key = key
94+
m.ValueKind = reflect.TypeOf(value).Kind()
95+
m.ValueString = fmt.Sprint(value)
96+
}
97+
98+
// EmitLazyLogger belongs to the log.Encoder interface
99+
func (m *MockKeyValue) EmitLazyLogger(value log.LazyLogger) {
100+
var meta MockKeyValue
101+
value(&meta)
102+
m.Key = meta.Key
103+
m.ValueKind = meta.ValueKind
104+
m.ValueString = meta.ValueString
105+
}

0 commit comments

Comments
 (0)