1package lightstep
2
3import (
4	"fmt"
5	"math"
6	"math/rand"
7	"os"
8	"path"
9	"strings"
10	"time" // N.B.(jmacd): Do not use google.golang.org/glog in this package.
11
12	"github.com/lightstep/lightstep-tracer-go/constants"
13	"github.com/opentracing/opentracing-go"
14	"google.golang.org/grpc"
15)
16
17// Default Option values.
18const (
19	DefaultCollectorPath     = "/_rpc/v1/reports/binary"
20	DefaultPlainPort         = 80
21	DefaultSecurePort        = 443
22	DefaultGRPCCollectorHost = "collector-grpc.lightstep.com"
23
24	DefaultSystemMetricsHost = "ingest.lightstep.com"
25
26	DefaultSystemMetricsMeasurementFrequency = 30 * time.Second
27	DefaultSystemMetricsTimeout              = 5 * time.Second
28
29	DefaultMaxReportingPeriod = 2500 * time.Millisecond
30	DefaultMinReportingPeriod = 500 * time.Millisecond
31	DefaultMaxSpans           = 1000
32	DefaultReportTimeout      = 30 * time.Second
33	DefaultReconnectPeriod    = 5 * time.Minute
34
35	DefaultMaxLogKeyLen   = 256
36	DefaultMaxLogValueLen = 1024
37	DefaultMaxLogsPerSpan = 500
38
39	DefaultGRPCMaxCallSendMsgSizeBytes = math.MaxInt32
40)
41
42// Tag and Tracer Attribute keys.
43const (
44	ParentSpanGUIDKey = "parent_span_guid"         // ParentSpanGUIDKey is the tag key used to record the relationship between child and parent spans.
45	ComponentNameKey  = constants.ComponentNameKey // NOTE: these will be deprecated in favour of the constants package
46	GUIDKey           = "lightstep.guid"           // <- runtime guid, not span guid
47	HostnameKey       = constants.HostnameKey      // NOTE: these will be deprecated in favour of the constants package
48	CommandLineKey    = "lightstep.command_line"
49	ServiceVersionKey = constants.ServiceVersionKey // NOTE: these will be deprecated in favour of the constants package
50
51	TracerPlatformKey        = "lightstep.tracer_platform"
52	TracerPlatformValue      = "go"
53	TracerPlatformVersionKey = "lightstep.tracer_platform_version"
54	TracerVersionKey         = "lightstep.tracer_version" // Note: TracerVersionValue is generated from ./VERSION
55)
56
57const (
58	secureScheme    = "https"
59	plaintextScheme = "http"
60)
61
62// Validation Errors
63var (
64	errInvalidGUIDKey = fmt.Errorf("Options invalid: setting the %v tag is no longer supported", GUIDKey)
65)
66
67// A SpanRecorder handles all of the `RawSpan` data generated via an
68// associated `Tracer` instance.
69type SpanRecorder interface {
70	RecordSpan(RawSpan)
71}
72
73// Endpoint describes a collector or web API host/port and whether or
74// not to use plaintext communication.
75type Endpoint struct {
76	Scheme           string `yaml:"scheme" json:"scheme" usage:"scheme to use for the endpoint, defaults to appropriate one if no custom one is required"`
77	Host             string `yaml:"host" json:"host" usage:"host on which the endpoint is running"`
78	Port             int    `yaml:"port" json:"port" usage:"port on which the endpoint is listening"`
79	Plaintext        bool   `yaml:"plaintext" json:"plaintext" usage:"whether or not to encrypt data send to the endpoint"`
80	CustomCACertFile string `yaml:"custom_ca_cert_file" json:"custom_ca_cert_file" usage:"path to a custom CA cert file, defaults to system defined certs if omitted"`
81}
82
83// HostPort use SocketAddress instead.
84// DEPRECATED
85func (e Endpoint) HostPort() string {
86	return e.SocketAddress()
87}
88
89// SocketAddress returns an address suitable for dialing grpc connections
90func (e Endpoint) SocketAddress() string {
91	return fmt.Sprintf("%s:%d", e.Host, e.Port)
92}
93
94// URL returns an address suitable for dialing http connections
95func (e Endpoint) URL() string {
96	return fmt.Sprintf("%s%s", e.urlWithoutPath(), DefaultCollectorPath)
97}
98
99// urlWithoutPath returns an address suitable for grpc connections if a custom scheme is provided
100func (e Endpoint) urlWithoutPath() string {
101	return fmt.Sprintf("%s://%s", e.scheme(), e.SocketAddress())
102}
103
104func (e Endpoint) scheme() string {
105	if len(e.Scheme) > 0 {
106		return e.Scheme
107	}
108
109	if e.Plaintext {
110		return plaintextScheme
111	}
112
113	return secureScheme
114}
115
116type SystemMetricsOptions struct {
117	Disabled             bool          `yaml:"disabled"`
118	Endpoint             Endpoint      `yaml:"endpoint"`
119	MeasurementFrequency time.Duration `yaml:"measurement_frequency"`
120	Timeout              time.Duration `yaml:"timeout"`
121}
122
123// Options control how the LightStep Tracer behaves.
124type Options struct {
125	// AccessToken is the unique API key for your LightStep project.  It is
126	// available on your account page at https://app.lightstep.com/account
127	AccessToken string `yaml:"access_token" usage:"access token for reporting to LightStep"`
128
129	// Collector is the host, port, and plaintext option to use
130	// for the collector.
131	Collector Endpoint `yaml:"collector"`
132
133	// Tags are arbitrary key-value pairs that apply to all spans generated by
134	// this Tracer.
135	Tags opentracing.Tags
136
137	// LightStep is the host, port, and plaintext option to use
138	// for the LightStep web API.
139	LightStepAPI Endpoint `yaml:"lightstep_api"`
140
141	// MaxBufferedSpans is the maximum number of spans that will be buffered
142	// before sending them to a collector.
143	MaxBufferedSpans int `yaml:"max_buffered_spans"`
144
145	// MaxLogKeyLen is the maximum allowable size (in characters) of an
146	// OpenTracing logging key. Longer keys are truncated.
147	MaxLogKeyLen int `yaml:"max_log_key_len"`
148
149	// MaxLogValueLen is the maximum allowable size (in characters) of an
150	// OpenTracing logging value. Longer values are truncated. Only applies to
151	// variable-length value types (strings, interface{}, etc).
152	MaxLogValueLen int `yaml:"max_log_value_len"`
153
154	// MaxLogsPerSpan limits the number of logs in a single span.
155	MaxLogsPerSpan int `yaml:"max_logs_per_span"`
156
157	// GRPCMaxCallSendMsgSizeBytes limits the size in bytes of grpc messages
158	// sent by a client.
159	GRPCMaxCallSendMsgSizeBytes int `yaml:"grpc_max_call_send_msg_size_bytes"`
160
161	// ReportingPeriod is the maximum duration of time between sending spans
162	// to a collector.  If zero, the default will be used.
163	ReportingPeriod time.Duration `yaml:"reporting_period"`
164
165	// MinReportingPeriod is the minimum duration of time between sending spans
166	// to a collector.  If zero, the default will be used. It is strongly
167	// recommended to use the default.
168	MinReportingPeriod time.Duration `yaml:"min_reporting_period"`
169
170	ReportTimeout time.Duration `yaml:"report_timeout"`
171
172	// DropSpanLogs turns log events on all Spans into no-ops.
173	DropSpanLogs bool `yaml:"drop_span_logs"`
174
175	// DEPRECATED: The LightStep library prints the first error to stdout by default.
176	// See the documentation on the SetGlobalEventHandler function for guidance on
177	// how to integrate tracer diagnostics with your application's logging and
178	// metrics systems.
179	Verbose bool `yaml:"verbose"`
180
181	// Force the use of a specific transport protocol. If multiple are set to true,
182	// the following order is used to select for the first option: http, grpc.
183	// If none are set to true, HTTP is defaulted to.
184	UseHttp bool `yaml:"use_http"`
185	UseGRPC bool `yaml:"usegrpc"`
186
187	// Propagators allow inject/extract to use custom propagators for different formats. This
188	// package includes a `B3Propagator` that supports B3 headers on text maps and http headers.
189	// Defaults:
190	//   opentracing.HTTPHeaders: LightStepPropagator
191	//   opentracing.TextMap: LightStepPropagator,
192	//   opentracing.Binary: LightStepPropagator
193	Propagators map[opentracing.BuiltinFormat]Propagator `yaml:"-"`
194
195	// CustomCollector allows customizing the Protobuf transport.
196	// This is an advanced feature that avoids reconnect logic.
197	CustomCollector Collector `yaml:"-" json:"-"`
198
199	ReconnectPeriod time.Duration `yaml:"reconnect_period"`
200
201	// DialOptions allows customizing the grpc dial options passed to the grpc.Dial(...) call.
202	// This is an advanced feature added to allow for a custom balancer or middleware.
203	// It can be safely ignored if you have no custom dialing requirements.
204	// If UseGRPC is not set, these dial options are ignored.
205	DialOptions []grpc.DialOption `yaml:"-" json:"-"`
206
207	// A hook for receiving finished span events
208	Recorder SpanRecorder `yaml:"-" json:"-"`
209
210	// For testing purposes only
211	ConnFactory ConnectorFactory `yaml:"-" json:"-"`
212
213	// Enable LightStep Meta Event Logging
214	MetaEventReportingEnabled bool `yaml:"meta_event_reporting_enabled" json:"meta_event_reporting_enabled"`
215
216	SystemMetrics SystemMetricsOptions `yaml:"system_metrics"`
217}
218
219// Initialize validates options, and sets default values for unset options.
220// This is called automatically when creating a new Tracer.
221func (opts *Options) Initialize() error {
222	err := opts.Validate()
223	if err != nil {
224		return err
225	}
226
227	// Note: opts is a copy of the user's data, ok to modify.
228	if opts.MaxBufferedSpans == 0 {
229		opts.MaxBufferedSpans = DefaultMaxSpans
230	}
231	if opts.MaxLogKeyLen == 0 {
232		opts.MaxLogKeyLen = DefaultMaxLogKeyLen
233	}
234	if opts.MaxLogValueLen == 0 {
235		opts.MaxLogValueLen = DefaultMaxLogValueLen
236	}
237	if opts.MaxLogsPerSpan == 0 {
238		opts.MaxLogsPerSpan = DefaultMaxLogsPerSpan
239	}
240	if opts.GRPCMaxCallSendMsgSizeBytes == 0 {
241		opts.GRPCMaxCallSendMsgSizeBytes = DefaultGRPCMaxCallSendMsgSizeBytes
242	}
243	if opts.ReportingPeriod == 0 {
244		opts.ReportingPeriod = DefaultMaxReportingPeriod
245	}
246	if opts.MinReportingPeriod == 0 {
247		opts.MinReportingPeriod = DefaultMinReportingPeriod
248	}
249	if opts.ReportTimeout == 0 {
250		opts.ReportTimeout = DefaultReportTimeout
251	}
252	if opts.ReconnectPeriod == 0 {
253		opts.ReconnectPeriod = DefaultReconnectPeriod
254	}
255	if opts.Tags == nil {
256		opts.Tags = map[string]interface{}{}
257	}
258
259	// Set some default attributes if not found in options
260	if _, found := opts.Tags[constants.ComponentNameKey]; !found {
261		// If not found, use the first command line argument as the default service name
262		defaultService := path.Base(os.Args[0])
263
264		emitEvent(newEventMissingService(defaultService))
265		opts.Tags[constants.ComponentNameKey] = defaultService
266	}
267	if _, found := opts.Tags[constants.HostnameKey]; !found {
268		hostname, _ := os.Hostname()
269		opts.Tags[constants.HostnameKey] = hostname
270	}
271	if _, found := opts.Tags[CommandLineKey]; !found {
272		opts.Tags[CommandLineKey] = strings.Join(os.Args, " ")
273	}
274
275	opts.ReconnectPeriod = time.Duration(float64(opts.ReconnectPeriod) * (1 + 0.2*rand.Float64()))
276
277	if opts.Collector.Host == "" {
278		opts.Collector.Host = DefaultGRPCCollectorHost
279	}
280
281	if opts.Collector.Port <= 0 {
282		if opts.Collector.Plaintext {
283			opts.Collector.Port = DefaultPlainPort
284		} else {
285			opts.Collector.Port = DefaultSecurePort
286		}
287	}
288
289	if opts.SystemMetrics.Endpoint.Host == "" {
290		opts.SystemMetrics.Endpoint.Host = DefaultSystemMetricsHost
291	}
292
293	if opts.SystemMetrics.Endpoint.Port <= 0 {
294		opts.SystemMetrics.Endpoint.Port = DefaultSecurePort
295
296		if opts.SystemMetrics.Endpoint.Plaintext {
297			opts.SystemMetrics.Endpoint.Port = DefaultPlainPort
298		}
299	}
300
301	if opts.SystemMetrics.MeasurementFrequency <= 0 {
302		opts.SystemMetrics.MeasurementFrequency = DefaultSystemMetricsMeasurementFrequency
303	}
304
305	if opts.SystemMetrics.Timeout <= 0 {
306		opts.SystemMetrics.Timeout = DefaultSystemMetricsTimeout
307	}
308
309	return nil
310}
311
312// Validate checks that all required fields are set, and no options are incorrectly
313// configured.
314func (opts *Options) Validate() error {
315	if _, found := opts.Tags[GUIDKey]; found {
316		return errInvalidGUIDKey
317	}
318
319	if len(opts.Collector.CustomCACertFile) != 0 {
320		if _, err := os.Stat(opts.Collector.CustomCACertFile); os.IsNotExist(err) {
321			return err
322		}
323	}
324
325	if !opts.SystemMetrics.Disabled && len(opts.SystemMetrics.Endpoint.CustomCACertFile) != 0 {
326		if _, err := os.Stat(opts.SystemMetrics.Endpoint.CustomCACertFile); os.IsNotExist(err) {
327			return err
328		}
329	}
330
331	return nil
332}
333
334// SetSpanID is a opentracing.StartSpanOption that sets an
335// explicit SpanID.  It must be used in conjunction with
336// SetTraceID or the result is undefined.
337type SetSpanID uint64
338
339// Apply satisfies the StartSpanOption interface.
340func (sid SetSpanID) Apply(sso *opentracing.StartSpanOptions) {}
341func (sid SetSpanID) applyLS(sso *startSpanOptions) {
342	sso.SetSpanID = uint64(sid)
343}
344
345// SetTraceID is an opentracing.StartSpanOption that sets an
346// explicit TraceID.  It must be used in order to set an
347// explicit SpanID or ParentSpanID.  If a ChildOf or
348// FollowsFrom span relation is also set in the start options,
349// it will override this value.
350type SetTraceID uint64
351
352// Apply satisfies the StartSpanOption interface.
353func (sid SetTraceID) Apply(sso *opentracing.StartSpanOptions) {}
354func (sid SetTraceID) applyLS(sso *startSpanOptions) {
355	sso.SetTraceID = uint64(sid)
356}
357
358// SetParentSpanID is an opentracing.StartSpanOption that sets
359// an explicit parent SpanID.  It must be used in conjunction
360// with SetTraceID or the result is undefined.  If the value
361// is zero, it will be disregarded.  If a ChildOf or
362// FollowsFrom span relation is also set in the start options,
363// it will override this value.
364type SetParentSpanID uint64
365
366// Apply satisfies the StartSpanOption interface.
367func (sid SetParentSpanID) Apply(sso *opentracing.StartSpanOptions) {}
368func (sid SetParentSpanID) applyLS(sso *startSpanOptions) {
369	sso.SetParentSpanID = uint64(sid)
370}
371
372// lightStepStartSpanOption is used to identify lightstep-specific Span options.
373type lightStepStartSpanOption interface {
374	applyLS(*startSpanOptions)
375}
376
377type SetSampled string
378func (s SetSampled) Apply(sso *opentracing.StartSpanOptions) {}
379
380func (s SetSampled) applyLS(sso *startSpanOptions) {
381	sso.SetSampled = string(s)
382}
383
384type startSpanOptions struct {
385	Options opentracing.StartSpanOptions
386
387	// Options to explicitly set span_id, trace_id,
388	// parent_span_id, expected to be used when exporting spans
389	// from another system into LightStep via opentracing APIs.
390	SetSpanID       uint64
391	SetParentSpanID uint64
392	SetTraceID      uint64
393	SetSampled	string
394}
395
396func newStartSpanOptions(sso []opentracing.StartSpanOption) startSpanOptions {
397	opts := startSpanOptions{}
398	for _, o := range sso {
399		switch o := o.(type) {
400		case lightStepStartSpanOption:
401			o.applyLS(&opts)
402		default:
403			o.Apply(&opts.Options)
404		}
405	}
406	return opts
407}
408