1package log
2
3import (
4	"fmt"
5	"math"
6)
7
8type fieldType int
9
10const (
11	stringType fieldType = iota
12	boolType
13	intType
14	int32Type
15	uint32Type
16	int64Type
17	uint64Type
18	float32Type
19	float64Type
20	errorType
21	objectType
22	lazyLoggerType
23	noopType
24)
25
26// Field instances are constructed via LogBool, LogString, and so on.
27// Tracing implementations may then handle them via the Field.Marshal
28// method.
29//
30// "heavily influenced by" (i.e., partially stolen from)
31// https://github.com/uber-go/zap
32type Field struct {
33	key          string
34	fieldType    fieldType
35	numericVal   int64
36	stringVal    string
37	interfaceVal interface{}
38}
39
40// String adds a string-valued key:value pair to a Span.LogFields() record
41func String(key, val string) Field {
42	return Field{
43		key:       key,
44		fieldType: stringType,
45		stringVal: val,
46	}
47}
48
49// Bool adds a bool-valued key:value pair to a Span.LogFields() record
50func Bool(key string, val bool) Field {
51	var numericVal int64
52	if val {
53		numericVal = 1
54	}
55	return Field{
56		key:        key,
57		fieldType:  boolType,
58		numericVal: numericVal,
59	}
60}
61
62// Int adds an int-valued key:value pair to a Span.LogFields() record
63func Int(key string, val int) Field {
64	return Field{
65		key:        key,
66		fieldType:  intType,
67		numericVal: int64(val),
68	}
69}
70
71// Int32 adds an int32-valued key:value pair to a Span.LogFields() record
72func Int32(key string, val int32) Field {
73	return Field{
74		key:        key,
75		fieldType:  int32Type,
76		numericVal: int64(val),
77	}
78}
79
80// Int64 adds an int64-valued key:value pair to a Span.LogFields() record
81func Int64(key string, val int64) Field {
82	return Field{
83		key:        key,
84		fieldType:  int64Type,
85		numericVal: val,
86	}
87}
88
89// Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record
90func Uint32(key string, val uint32) Field {
91	return Field{
92		key:        key,
93		fieldType:  uint32Type,
94		numericVal: int64(val),
95	}
96}
97
98// Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record
99func Uint64(key string, val uint64) Field {
100	return Field{
101		key:        key,
102		fieldType:  uint64Type,
103		numericVal: int64(val),
104	}
105}
106
107// Float32 adds a float32-valued key:value pair to a Span.LogFields() record
108func Float32(key string, val float32) Field {
109	return Field{
110		key:        key,
111		fieldType:  float32Type,
112		numericVal: int64(math.Float32bits(val)),
113	}
114}
115
116// Float64 adds a float64-valued key:value pair to a Span.LogFields() record
117func Float64(key string, val float64) Field {
118	return Field{
119		key:        key,
120		fieldType:  float64Type,
121		numericVal: int64(math.Float64bits(val)),
122	}
123}
124
125// Error adds an error with the key "error.object" to a Span.LogFields() record
126func Error(err error) Field {
127	return Field{
128		key:          "error.object",
129		fieldType:    errorType,
130		interfaceVal: err,
131	}
132}
133
134// Object adds an object-valued key:value pair to a Span.LogFields() record
135// Please pass in an immutable object, otherwise there may be concurrency issues.
136// Such as passing in the map, log.Object may result in "fatal error: concurrent map iteration and map write".
137// Because span is sent asynchronously, it is possible that this map will also be modified.
138func Object(key string, obj interface{}) Field {
139	return Field{
140		key:          key,
141		fieldType:    objectType,
142		interfaceVal: obj,
143	}
144}
145
146// Event creates a string-valued Field for span logs with key="event" and value=val.
147func Event(val string) Field {
148	return String("event", val)
149}
150
151// Message creates a string-valued Field for span logs with key="message" and value=val.
152func Message(val string) Field {
153	return String("message", val)
154}
155
156// LazyLogger allows for user-defined, late-bound logging of arbitrary data
157type LazyLogger func(fv Encoder)
158
159// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
160// implementation will call the LazyLogger function at an indefinite time in
161// the future (after Lazy() returns).
162func Lazy(ll LazyLogger) Field {
163	return Field{
164		fieldType:    lazyLoggerType,
165		interfaceVal: ll,
166	}
167}
168
169// Noop creates a no-op log field that should be ignored by the tracer.
170// It can be used to capture optional fields, for example those that should
171// only be logged in non-production environment:
172//
173//     func customerField(order *Order) log.Field {
174//          if os.Getenv("ENVIRONMENT") == "dev" {
175//              return log.String("customer", order.Customer.ID)
176//          }
177//          return log.Noop()
178//     }
179//
180//     span.LogFields(log.String("event", "purchase"), customerField(order))
181//
182func Noop() Field {
183	return Field{
184		fieldType: noopType,
185	}
186}
187
188// Encoder allows access to the contents of a Field (via a call to
189// Field.Marshal).
190//
191// Tracer implementations typically provide an implementation of Encoder;
192// OpenTracing callers typically do not need to concern themselves with it.
193type Encoder interface {
194	EmitString(key, value string)
195	EmitBool(key string, value bool)
196	EmitInt(key string, value int)
197	EmitInt32(key string, value int32)
198	EmitInt64(key string, value int64)
199	EmitUint32(key string, value uint32)
200	EmitUint64(key string, value uint64)
201	EmitFloat32(key string, value float32)
202	EmitFloat64(key string, value float64)
203	EmitObject(key string, value interface{})
204	EmitLazyLogger(value LazyLogger)
205}
206
207// Marshal passes a Field instance through to the appropriate
208// field-type-specific method of an Encoder.
209func (lf Field) Marshal(visitor Encoder) {
210	switch lf.fieldType {
211	case stringType:
212		visitor.EmitString(lf.key, lf.stringVal)
213	case boolType:
214		visitor.EmitBool(lf.key, lf.numericVal != 0)
215	case intType:
216		visitor.EmitInt(lf.key, int(lf.numericVal))
217	case int32Type:
218		visitor.EmitInt32(lf.key, int32(lf.numericVal))
219	case int64Type:
220		visitor.EmitInt64(lf.key, int64(lf.numericVal))
221	case uint32Type:
222		visitor.EmitUint32(lf.key, uint32(lf.numericVal))
223	case uint64Type:
224		visitor.EmitUint64(lf.key, uint64(lf.numericVal))
225	case float32Type:
226		visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
227	case float64Type:
228		visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
229	case errorType:
230		if err, ok := lf.interfaceVal.(error); ok {
231			visitor.EmitString(lf.key, err.Error())
232		} else {
233			visitor.EmitString(lf.key, "<nil>")
234		}
235	case objectType:
236		visitor.EmitObject(lf.key, lf.interfaceVal)
237	case lazyLoggerType:
238		visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
239	case noopType:
240		// intentionally left blank
241	}
242}
243
244// Key returns the field's key.
245func (lf Field) Key() string {
246	return lf.key
247}
248
249// Value returns the field's value as interface{}.
250func (lf Field) Value() interface{} {
251	switch lf.fieldType {
252	case stringType:
253		return lf.stringVal
254	case boolType:
255		return lf.numericVal != 0
256	case intType:
257		return int(lf.numericVal)
258	case int32Type:
259		return int32(lf.numericVal)
260	case int64Type:
261		return int64(lf.numericVal)
262	case uint32Type:
263		return uint32(lf.numericVal)
264	case uint64Type:
265		return uint64(lf.numericVal)
266	case float32Type:
267		return math.Float32frombits(uint32(lf.numericVal))
268	case float64Type:
269		return math.Float64frombits(uint64(lf.numericVal))
270	case errorType, objectType, lazyLoggerType:
271		return lf.interfaceVal
272	case noopType:
273		return nil
274	default:
275		return nil
276	}
277}
278
279// String returns a string representation of the key and value.
280func (lf Field) String() string {
281	return fmt.Sprint(lf.key, ":", lf.Value())
282}
283