-
Notifications
You must be signed in to change notification settings - Fork 288
Add support for client configuration via env vars #275
Add support for client configuration via env vars #275
Conversation
Codecov Report
@@ Coverage Diff @@
## master #275 +/- ##
==========================================
+ Coverage 85.74% 86.14% +0.39%
==========================================
Files 54 55 +1
Lines 2856 2952 +96
==========================================
+ Hits 2449 2543 +94
- Misses 287 288 +1
- Partials 120 121 +1
Continue to review full report at Codecov.
|
config/config_test.go
Outdated
@@ -70,6 +71,77 @@ func TestDefaultSampler(t *testing.T) { | |||
require.Error(t, err) | |||
} | |||
|
|||
func TestServiceNameFromEnv(t *testing.T) { | |||
os.Setenv("JAEGER_SERVICE_NAME", "my-service") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use local constants for these ENV names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Java, yes. In Go? I don't know... I have a feeling that creating a constant JAEGER_SERVICE_NAME
with the value JAEGER_SERVICE_NAME
is not idiomatic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constant name can be different, go idiomatic. I am asking for it because there is some repetition - code, tests, logs, comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, typically we try to avoid string constants directly in the code. Having them all in a single place as constants makes it easier to survey and document
config/config.go
Outdated
@@ -207,6 +226,55 @@ func (c Configuration) New( | |||
return tracer, closer, nil | |||
} | |||
|
|||
// FromEnv uses environment variables to set the tracer's Configuration | |||
func (c *Configuration) FromEnv() (*Configuration, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering whether:
func FromEnv() (*Configuration, error) {
would be better/cleaner. Then we don't have to care about overrides. It would also align more with java configuration (static fromEnv
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be, yes. This means that people can't load the env vars into an existing object. Is that OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can create configuration from ENV and then adjust behaviour in code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we then have SamplerConfigFromEnv()
/ ReporterConfigFromEnv()
/ ...FromEnv()
? Sounds good to me.
config/config.go
Outdated
} | ||
|
||
// NewFromEnv returns a new tracer configured using environment variables | ||
func (c *Configuration) NewFromEnv(options ...Option) (opentracing.Tracer, io.Closer, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 on this method, also fromEnv should return the error if the service name is not valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one exists because it has a different semantic as the other. The idea of FromEnv()
methods is to configure an existing object based on env vars. This one will create a new tracer (similar to the New()
method), running FromEnv()
before.
This means that it's still possible to use the New(serviceName, ...)
after calling FromEnv()
, so, FromEnv()
should not fail if an env var with the service name is not available.
I'm open to change this behavior, though. What do others say?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think it's redundant having FromEnv() that returns Config and NewFromEnv that returns a tracer, since the latter can be implemented as FromEnv().New()
- the existing method called New() is poorly named, imo, it should've been NewTracer()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's redundant having FromEnv() that returns Config and NewFromEnv that returns a tracer, since the latter can be implemented as FromEnv().New()
jaegerconfig.FromEnv().New()
would require a change in the signature of New()
to remove the service name from it. Or did you mean jaegerconfig.FromEnv().New(string, ...Options)
?
It's also not clear if it would be preferrable to have @pavolloffay's approach (jaegerconfig.FromEnv()
) or the existing one (&Configuration{}.FromEnv()
). I like @pavolloffay's suggestion, but I like consistency more :)
the existing method called New() is poorly named, imo, it should've been NewTracer()
Should I create a NewTracer()
, and make the existing New()
call it? How would deprecation work here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no I wasn't suggesting changing the signature of New(), we should keep it. Go doesn't have an official way of deprecating packages afaik, but the convention is to add the last comment line as Deprecated: explanation
On static func vs. member func, I don't have a strong opinion - preferably we should be consistent with other clients.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since the latter can be implemented as FromEnv().New()
This is the part that I'm not quite following. New(string, ...Option)
requires a service name as the first parameter. Using FromEnv()
, I assume we'd be getting the service name from the env var, but we would still require the caller to pass a service name.
One solution is to allow the caller to pass an empty value, but that doesn't sound that clean to me.
Should I just create a NewTracer(...Option)
, without serviceName
?
1e1299d
to
e85b0c6
Compare
config/config_test.go
Outdated
func TestServiceNameFromEnv(t *testing.T) { | ||
os.Setenv(envServiceName, "my-service") | ||
|
||
_, c, err := FromEnv().New("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite like that callers are required to pass an empty string if they expect the service name to be specified via env var, but perhaps it's acceptable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as the service name is a member of configuration, we could deprecate New
and create newTracer
without requiring serviceName.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh you had the same suggestion here #275 (comment)
I like it
This is now ready for the next round of reviews, but please don't merge. I'll squash it into a single commit. edit: just realized I did not add any documentation about this feature to the readme files... So, please do start the review, but keep in mind I'll work on the readme. |
config/config.go
Outdated
// Configuration configures and creates Jaeger Tracer | ||
type Configuration struct { | ||
Disabled bool `yaml:"disabled"` | ||
// ServiceName specifies the service name to use on the tracer. | ||
// Can be set by exporting an environment variable named JAEGER_SERVICE_NAME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can be provided via environment variable named JAEGER_SERVICE_NAME
config/config.go
Outdated
// RPCMetrics can be set by exporting an environment variable named JAEGER_RPC_METRICS | ||
RPCMetrics bool `yaml:"rpc_metrics"` | ||
|
||
Tags []opentracing.Tag `yaml:"tags"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- add comment about env var?
- add blank line after
config/config.go
Outdated
} | ||
|
||
// FromEnv uses environment variables to set the tracer's Configuration | ||
func FromEnv() *Configuration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - this is making the file huge, I suggest moving to config_env.go
(including constants) for a nice encapsulation
config/config.go
Outdated
if e := os.Getenv(envServiceName); e != "" { | ||
// We are ignoring any errors, so that we have a single return value | ||
// This pattern repeats itself across all the "FromEnv" functions. | ||
// TODO: should we at least log when something bad happens, instead of just ignoring? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's best not to ignore errors. Many env vars are parsed (e.g. as boolean), it's better to fail if we can't parse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means then that FromEnv()
has a multiple valued return, making it impossible to do FromEnv().NewTracer()
, no ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, but it's better than swallowing errors. FromEnv().NewTracer()
is not exactly the goal here. If people really want a one-liner, we could add NewTracerFromEnv()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, just wanted to make sure we were on the same page :)
config/config.go
Outdated
} | ||
|
||
// SamplerConfigFromEnv creates a new SamplerConfig based on the environment variables | ||
func SamplerConfigFromEnv() *SamplerConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: how about we keep these private for now? Can always expose later if someone actually needs that
config/config.go
Outdated
// spec for this value: | ||
// - comma separated list of key=value | ||
// - value can be specified using the notation ${envVar:defaultValue}, where `envVar` | ||
// is an environment variable and `defaultValue` is the value to use in case the env var is not set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to method comment
config/config.go
Outdated
return nil, nil, errors.New("no service name provided") | ||
} | ||
|
||
return c.New(c.ServiceName, options...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it would be better to do the reverse - implement New via NewTracer
config/config_test.go
Outdated
ts := parseTags(tags) | ||
require.Equal(t, 4, len(ts)) | ||
|
||
require.Equal(t, "key", ts[0].Key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: require
means you cannot continue the test further, should be rarely used, in most cases assert
is better.
config/config_test.go
Outdated
func TestNewTracerWithoutServiceName(t *testing.T) { | ||
cfg := &Configuration{} | ||
_, _, err := cfg.NewTracer(Metrics(metrics.NullFactory), Logger(log.NullLogger)) | ||
require.Error(t, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this test may succeed as false positive. Best to check not only that there was an error, but that the error contained a certain string, e.g. assert.Contains(t, err.Error(), "pattern")
0a861a7
to
c844e00
Compare
c844e00
to
7fe69a0
Compare
I believe all the comments were addressed by this last change. |
README.md
Outdated
JAEGER_SAMPLER_MAX_OPERATIONS | The maximum number of operations that the sampler will keep track of | ||
JAEGER_SAMPLER_REFRESH_INTERVAL | How often the remotely controlled sampler will poll jaeger-agent for the appropriate sampling strategy | ||
JAEGER_TAGS | A comma separated list of `name = value` tracer level tags, which get added to all reported spans. The value can also refer to an environment variable using the format `${envVarName:default}`, where the `:default` is optional, and identifies a value to be used if the environment variable cannot be found | ||
JAEGER_DISABLED | Whether the tracer is disabled or not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might want to clarify: if true, the default opentracing.NoopTracer
is used.
config/config.go
Outdated
@@ -115,9 +135,23 @@ func (c Configuration) New( | |||
serviceName string, | |||
options ...Option, | |||
) (opentracing.Tracer, io.Closer, error) { | |||
if serviceName == "" { | |||
if serviceName == "" && c.ServiceName == "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this check is not necessary, since it will be done by NewTracer anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but given that we have ServiceName
as part of the Configuration
object, this would cover the following scenario:
cfg := &Configuration{ServiceName: "my-service", ...}
_, closer, err := cfg.New("")
Without the right side of the check, the ServiceName
from the Configuration
is ignored and the error is returned ("no service name provided").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a new test for this scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure I follow. If serviceName arg is not blank, it will override c.ServiceName, otherwise it wouldn't. In either case it will be followed by a call to c.NewTracer()
which will return an error if c.ServiceName
is blank.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you call NewTracer()
, yes. But New("")
might be called directly:
cfg := &Configuration{ServiceName: "my-service", ...}
_, closer, err := cfg.New("")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but New() delegates to NewTracer():
return c.NewTracer(options...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config/config.go
Outdated
return c.NewTracer(options...) | ||
} | ||
|
||
// NewTracer returns a new tracer based on the current configuration, using the given options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add info about closer function (see comment for New)
@@ -115,9 +135,23 @@ func (c Configuration) New( | |||
serviceName string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add // Deprecated: use NewTracer() function
as the last line in the comment
config/config_env.go
Outdated
if value, err := strconv.ParseInt(e, 10, 0); err == nil { | ||
rc.QueueSize = int(value) | ||
} else { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: a better way would be to mention the field, e.g.
return nil, errors.Wraps(err, "Cannot parse env var %s=%s", envReporterMaxQueueSize, e)
config/config_env.go
Outdated
// JAEGER_AGENT_HOST:JAEGER_AGENT_PORT, like: localhost:6831 | ||
if e := os.Getenv(envAgentHost); e != "" { | ||
host := e | ||
port := 6831 // TODO: can we somehow consume the default from the transport itself? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is const defaultUDPSpanServerHostPort = "localhost:6831"
, the port could be refactored into a public const (maybe even moved to constants.go
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The host as well then, for consistency :)
config/config_env.go
Outdated
} | ||
|
||
// JAEGER_AGENT_HOST:JAEGER_AGENT_PORT, like: localhost:6831 | ||
if e := os.Getenv(envAgentHost); e != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: so if only the port is provided via env it won't be read here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a moment, I thought this was in line with what the Java client was doing, but I'm clearly wrong.
// parseTags parses the given string into a collection of Tags. | ||
// Spec for this value: | ||
// - comma separated list of key=value | ||
// - value can be specified using the notation ${envVar:defaultValue}, where `envVar` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
${envVar:defaultValue}
- do we really need this? I.e. couldn't this be delegated to the shells where the env vars are defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. This was added by @objectiser, IIRC, so, he might have a case for it. If we decide to remove, I'd do it in a separate PR, as we should also remove it from the Java client.
config/config_test.go
Outdated
{ | ||
envVar: envRPCMetrics, | ||
value: "NOT_A_BOOLEAN", | ||
expectedErr: "strconv.ParseBool", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my comment about wrapping errors - if it's implemented, the tests could be written without dependency on the internal wording of Go errors, we can simply be checking for our own strings like Cannot parse env var JAEGER_RPC_METRICS= NOT_A_BOOLEAN
7fe69a0
to
b255cfa
Compare
PR updated. I left the |
Adds support for configuring the tracer based on environment variables, similar to what is currently supported by the Java client. Closes jaegertracing#206 Signed-off-by: Juraci Paixão Kröhling <[email protected]>
b255cfa
to
2dd39a3
Compare
we should change JAEGER_SAMPLER_MANAGER_HOST_PORT to CONFIG_MGR_HOST_PORT, because that endpoint is used more than just for sampling configuration |
what release version is the change scheduled for? |
released as 2.13 |
Status: I'm opening this PR mostly to get an early feedback on the general direction. I have the feeling that my Java background might be getting in the way, so, I wanted to catch any fundamental problem early on :)
As it is, this PR implements the configuration of the following properties, like from the Java Client:
Additionally, these are the properties that are unique to the Go client:
The following properties will not be implemented, as the underlying feature is not available for the Go client:
JAEGER_ENDPOINTJAEGER_AUTH_TOKENJAEGER_USERJAEGER_PASSWORDJAEGER_PROPAGATIONThe following property will not be implemented, as the underlying feature behaves differently in the Go client than in the Java client:
JAEGER_DISABLE_GLOBAL_TRACERCloses #206
Signed-off-by: Juraci Paixão Kröhling [email protected]