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" to a Span.LogFields() record
126func Error(err error) Field {
127	return Field{
128		key:          "error",
129		fieldType:    errorType,
130		interfaceVal: err,
131	}
132}
133
134// Object adds an object-valued key:value pair to a Span.LogFields() record
135func Object(key string, obj interface{}) Field {
136	return Field{
137		key:          key,
138		fieldType:    objectType,
139		interfaceVal: obj,
140	}
141}
142
143// LazyLogger allows for user-defined, late-bound logging of arbitrary data
144type LazyLogger func(fv Encoder)
145
146// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
147// implementation will call the LazyLogger function at an indefinite time in
148// the future (after Lazy() returns).
149func Lazy(ll LazyLogger) Field {
150	return Field{
151		fieldType:    lazyLoggerType,
152		interfaceVal: ll,
153	}
154}
155
156// Noop creates a no-op log field that should be ignored by the tracer.
157// It can be used to capture optional fields, for example those that should
158// only be logged in non-production environment:
159//
160//     func customerField(order *Order) log.Field {
161//          if os.Getenv("ENVIRONMENT") == "dev" {
162//              return log.String("customer", order.Customer.ID)
163//          }
164//          return log.Noop()
165//     }
166//
167//     span.LogFields(log.String("event", "purchase"), customerField(order))
168//
169func Noop() Field {
170	return Field{
171		fieldType: noopType,
172	}
173}
174
175// Encoder allows access to the contents of a Field (via a call to
176// Field.Marshal).
177//
178// Tracer implementations typically provide an implementation of Encoder;
179// OpenTracing callers typically do not need to concern themselves with it.
180type Encoder interface {
181	EmitString(key, value string)
182	EmitBool(key string, value bool)
183	EmitInt(key string, value int)
184	EmitInt32(key string, value int32)
185	EmitInt64(key string, value int64)
186	EmitUint32(key string, value uint32)
187	EmitUint64(key string, value uint64)
188	EmitFloat32(key string, value float32)
189	EmitFloat64(key string, value float64)
190	EmitObject(key string, value interface{})
191	EmitLazyLogger(value LazyLogger)
192}
193
194// Marshal passes a Field instance through to the appropriate
195// field-type-specific method of an Encoder.
196func (lf Field) Marshal(visitor Encoder) {
197	switch lf.fieldType {
198	case stringType:
199		visitor.EmitString(lf.key, lf.stringVal)
200	case boolType:
201		visitor.EmitBool(lf.key, lf.numericVal != 0)
202	case intType:
203		visitor.EmitInt(lf.key, int(lf.numericVal))
204	case int32Type:
205		visitor.EmitInt32(lf.key, int32(lf.numericVal))
206	case int64Type:
207		visitor.EmitInt64(lf.key, int64(lf.numericVal))
208	case uint32Type:
209		visitor.EmitUint32(lf.key, uint32(lf.numericVal))
210	case uint64Type:
211		visitor.EmitUint64(lf.key, uint64(lf.numericVal))
212	case float32Type:
213		visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
214	case float64Type:
215		visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
216	case errorType:
217		if err, ok := lf.interfaceVal.(error); ok {
218			visitor.EmitString(lf.key, err.Error())
219		} else {
220			visitor.EmitString(lf.key, "<nil>")
221		}
222	case objectType:
223		visitor.EmitObject(lf.key, lf.interfaceVal)
224	case lazyLoggerType:
225		visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
226	case noopType:
227		// intentionally left blank
228	}
229}
230
231// Key returns the field's key.
232func (lf Field) Key() string {
233	return lf.key
234}
235
236// Value returns the field's value as interface{}.
237func (lf Field) Value() interface{} {
238	switch lf.fieldType {
239	case stringType:
240		return lf.stringVal
241	case boolType:
242		return lf.numericVal != 0
243	case intType:
244		return int(lf.numericVal)
245	case int32Type:
246		return int32(lf.numericVal)
247	case int64Type:
248		return int64(lf.numericVal)
249	case uint32Type:
250		return uint32(lf.numericVal)
251	case uint64Type:
252		return uint64(lf.numericVal)
253	case float32Type:
254		return math.Float32frombits(uint32(lf.numericVal))
255	case float64Type:
256		return math.Float64frombits(uint64(lf.numericVal))
257	case errorType, objectType, lazyLoggerType:
258		return lf.interfaceVal
259	case noopType:
260		return nil
261	default:
262		return nil
263	}
264}
265
266// String returns a string representation of the key and value.
267func (lf Field) String() string {
268	return fmt.Sprint(lf.key, ":", lf.Value())
269}
270