1// Copyright (c) The Thanos Authors.
2// Licensed under the Apache License 2.0.
3
4package jaeger
5
6import (
7	"fmt"
8	"net/url"
9	"os"
10	"strings"
11	"time"
12
13	"github.com/opentracing/opentracing-go"
14	"github.com/pkg/errors"
15	"github.com/uber/jaeger-client-go"
16	"github.com/uber/jaeger-client-go/config"
17	"gopkg.in/yaml.v2"
18)
19
20// Config - YAML configuration. For details see to https://github.com/jaegertracing/jaeger-client-go#environment-variables.
21type Config struct {
22	ServiceName            string        `yaml:"service_name"`
23	Disabled               bool          `yaml:"disabled"`
24	RPCMetrics             bool          `yaml:"rpc_metrics"`
25	Tags                   string        `yaml:"tags"`
26	SamplerType            string        `yaml:"sampler_type"`
27	SamplerParam           float64       `yaml:"sampler_param"`
28	SamplerManagerHostPort string        `yaml:"sampler_manager_host_port"`
29	SamplerMaxOperations   int           `yaml:"sampler_max_operations"`
30	SamplerRefreshInterval time.Duration `yaml:"sampler_refresh_interval"`
31	ReporterMaxQueueSize   int           `yaml:"reporter_max_queue_size"`
32	ReporterFlushInterval  time.Duration `yaml:"reporter_flush_interval"`
33	ReporterLogSpans       bool          `yaml:"reporter_log_spans"`
34	Endpoint               string        `yaml:"endpoint"`
35	User                   string        `yaml:"user"`
36	Password               string        `yaml:"password"`
37	AgentHost              string        `yaml:"agent_host"`
38	AgentPort              int           `yaml:"agent_port"`
39}
40
41// ParseConfigFromYaml uses config YAML to set the tracer's Configuration.
42func ParseConfigFromYaml(cfg []byte) (*config.Configuration, error) {
43	conf := &Config{}
44
45	if err := yaml.Unmarshal(cfg, &conf); err != nil {
46		return nil, err
47	}
48
49	c := &config.Configuration{}
50
51	if conf.ServiceName != "" {
52		c.ServiceName = conf.ServiceName
53	}
54
55	if conf.RPCMetrics {
56		c.RPCMetrics = conf.RPCMetrics
57	}
58
59	if conf.Disabled {
60		c.Disabled = conf.Disabled
61	}
62
63	if conf.Tags != "" {
64		c.Tags = parseTags(conf.Tags)
65	}
66
67	if s, err := samplerConfigFromConfig(*conf); err == nil {
68		c.Sampler = s
69	} else {
70		return nil, errors.Wrap(err, "cannot obtain sampler config from YAML")
71	}
72
73	if r, err := reporterConfigFromConfig(*conf); err == nil {
74		c.Reporter = r
75	} else {
76		return nil, errors.Wrap(err, "cannot obtain reporter config from YAML")
77	}
78
79	return c, nil
80}
81
82// samplerConfigFromConfig creates a new SamplerConfig based on the YAML Config.
83func samplerConfigFromConfig(cfg Config) (*config.SamplerConfig, error) {
84	sc := &config.SamplerConfig{}
85
86	if cfg.SamplerType != "" {
87		sc.Type = cfg.SamplerType
88	}
89
90	if cfg.SamplerParam != 0 {
91		sc.Param = cfg.SamplerParam
92	}
93
94	if cfg.SamplerManagerHostPort != "" {
95		sc.SamplingServerURL = cfg.SamplerManagerHostPort
96	}
97
98	if cfg.SamplerMaxOperations != 0 {
99		sc.MaxOperations = cfg.SamplerMaxOperations
100	}
101
102	if cfg.SamplerRefreshInterval != 0 {
103		sc.SamplingRefreshInterval = cfg.SamplerRefreshInterval
104	}
105
106	return sc, nil
107}
108
109// reporterConfigFromConfig creates a new ReporterConfig based on the YAML Config.
110func reporterConfigFromConfig(cfg Config) (*config.ReporterConfig, error) {
111	rc := &config.ReporterConfig{}
112
113	if cfg.ReporterMaxQueueSize != 0 {
114		rc.QueueSize = cfg.ReporterMaxQueueSize
115	}
116
117	if cfg.ReporterFlushInterval != 0 {
118		rc.BufferFlushInterval = cfg.ReporterFlushInterval
119	}
120
121	if cfg.ReporterLogSpans {
122		rc.LogSpans = cfg.ReporterLogSpans
123	}
124
125	if cfg.Endpoint != "" {
126		u, err := url.ParseRequestURI(cfg.Endpoint)
127		if err != nil {
128			return nil, errors.Wrapf(err, "cannot parse endpoint=%s", cfg.Endpoint)
129		}
130		rc.CollectorEndpoint = u.String()
131		user := cfg.User
132		pswd := cfg.Password
133		if user != "" && pswd == "" || user == "" && pswd != "" {
134			return nil, errors.Errorf("you must set %s and %s parameters together", cfg.User, cfg.Password)
135		}
136		rc.User = user
137		rc.Password = pswd
138	} else {
139		host := jaeger.DefaultUDPSpanServerHost
140		if cfg.AgentHost != "" {
141			host = cfg.AgentHost
142		}
143
144		port := jaeger.DefaultUDPSpanServerPort
145		if cfg.AgentPort != 0 {
146			port = cfg.AgentPort
147		}
148		rc.LocalAgentHostPort = fmt.Sprintf("%s:%d", host, port)
149	}
150
151	return rc, nil
152}
153
154// parseTags parses the given string into a collection of Tags.
155// Spec for this value:
156// - comma separated list of key=value
157// - value can be specified using the notation ${envVar:defaultValue}, where `envVar`
158// is an environment variable and `defaultValue` is the value to use in case the env var is not set.
159func parseTags(sTags string) []opentracing.Tag {
160	pairs := strings.Split(sTags, ",")
161	tags := make([]opentracing.Tag, 0)
162	for _, p := range pairs {
163		kv := strings.SplitN(p, "=", 2)
164		k, v := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
165
166		if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
167			ed := strings.SplitN(v[2:len(v)-1], ":", 2)
168			e, d := ed[0], ed[1]
169			v = os.Getenv(e)
170			if v == "" && d != "" {
171				v = d
172			}
173		}
174
175		tag := opentracing.Tag{Key: k, Value: v}
176		tags = append(tags, tag)
177	}
178
179	return tags
180}
181