-
Notifications
You must be signed in to change notification settings - Fork 848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add autoconfiguration wrapper artifact #2401
Conversation
|
||
private static void configureOtlpMetrics( | ||
ConfigProperties config, SdkMeterProvider meterProvider) { | ||
OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder(); |
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.
Will need to add linkage error guards to return better error messages for when a configuration option is set but implementation not available.
/cc @marcingrzejszczak you might be interested in this too |
@anuraaga thanks for mentioning me! BTW can you start mentioning @jonatan-ivanov too (that's why I suggested @jkwatson to create a Github Team and just add people to the team and mention that team instead of concrete people). |
It turns out that I don't have permissions to create a team (or at least I get a 404 from github when trying to click the "new team" link). So, @bogdandrutu will have to do this, I think, unless he gives me (and @anuraaga) owner permissions on the repository. |
.build(); | ||
} | ||
|
||
private static void configureOtlpMetrics( |
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 I'd break each of these methods out into their own class so it's clear how to add new ones to the auto-config, rather than having to dig into this beast to find them. :)
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { | ||
throw new IllegalStateException( | ||
"OpenTelemetrySdkAutoConfiguration detected on classpath " | ||
+ "but could not invoke initialize method. This is a bug in OpenTelemetry.", |
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 could also be due to a restrictive security manager
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 public method, as far as I know there wouldn't be any situation that could be blocked by security manager, is there one?
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 thought security managers could block all reflection, but I wouldn't swear to it.
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, a security manager can block all reflection, even for public methods, and even just calls to "getMethod", although I would suspect that's extremely rare these days.
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.
Had no idea - do you think I should change the message? Wondering how realistic it is to disable public reflection vs adding noise to this 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.
Maybe just "This could be a bug in OpenTelemetry" ? Really, it doesn't matter all that much.
this is pretty slick. It's SPI (albeit a slightly different flavor, with the builder), but it doesn't demand SPI be used. It makes it easy to manually auto-configure or manually configure, or mix-and-match however you like. 👍 to this approach. Obviously, if we want to go this route, we'll want to write some tests for the configuration (or move them over from the existing configuration code/tests), and do some refactor/cleanup so it's not one enormous class, but as a proof-of-concept, this is very nice. 🚀 |
… into autoconfigurre
… into autoconfigurre
Codecov Report
@@ Coverage Diff @@
## master #2401 +/- ##
=============================================
+ Coverage 0 87.85% +87.85%
- Complexity 0 2692 +2692
=============================================
Files 0 310 +310
Lines 0 9017 +9017
Branches 0 964 +964
=============================================
+ Hits 0 7922 +7922
- Misses 0 768 +768
- Partials 0 327 +327
Continue to review full report at Codecov.
|
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 have a bit more grungework in adding unit tests for ConfigProperties and some failure cases, but I think this is mostly ready for another look
set(autoConfigured); | ||
return autoConfigured; | ||
} | ||
|
||
OpenTelemetryFactory openTelemetryFactory = Utils.loadSpi(OpenTelemetryFactory.class); |
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.
Unlike the first commit with PoC, I haven't removed the API SPI yet, that we would do in a separate PR.
buildscripts/checkstyle.xml
Outdated
@@ -55,20 +55,6 @@ | |||
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> | |||
</module> | |||
<module name="NeedBraces"/> | |||
<module name="LeftCurly"/> |
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 #2418
/** | ||
* Auto-configuration for the OpenTelemetry SDK. As an alternative to programmatically configuring | ||
* the SDK using {@link OpenTelemetrySdk#builder()}, this package can be used to automatically | ||
* configure the SDK using environment properties specified by OpenTelemetry. |
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 hoping to link to a docs page from here rather than outlining all properties here - presuably autoconfiguration users are the last to read the javadocs
This is looking like a fantastic start. @anuraaga What do you think needs to be done to get it out of draft mode? I'd love to move forward with this ASAP, so we can move toward a 1.0 release. |
@jkwatson Just a few more boring unit tests I'll knock out today :) |
@jkwatson Ok should be good to go |
/** | ||
* A service provider interface (SPI) for performing additional programmatic configuration of a | ||
* {@link SdkTracerProviderBuilder} during initialization. When using auto-configuration, you should | ||
* prefer to use system properties for configuration, but this may be useful to register components |
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.
* prefer to use system properties for configuration, but this may be useful to register components | |
* prefer to use system properties or environment variables for configuration, but this may be useful to register components |
return new ConfigProperties((Map) properties, Collections.emptyMap()); | ||
} | ||
|
||
private final Map<String, String> config; |
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 should be up above the static methods
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.
Heh - I'm going to have trouble getting used to fields being so far from the constructor :P
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.
that's why static methods should go at the bottom of the class 😁
private final Map<String, String> config; | ||
|
||
private ConfigProperties( | ||
Map<Object, Object> systemProperties, Map<String, String> environmentVariables) { |
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 left map could be Map<?,?>
then you don't have to cast above.
splitKeyValuePairs -> { | ||
if (splitKeyValuePairs.size() != 2) { | ||
throw new ConfigurationException( | ||
"Map property key missing value: " + name + "=" + config.get(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.
small nit. if you have an entry like =f
it's technically missing the key, not the value. ;)
Arrays.stream(keyValuePair.split("=", 2)) | ||
.map(String::trim) | ||
.filter(s -> !s.isEmpty()) | ||
.collect(Collectors.toList())) |
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.
These 4 lines of code could be extracted to a method like "filterBlanksAndNulls" and re-used above at line 96-99
/** An exception that is thrown if the user-provided configuration is invalid. */ | ||
public final class ConfigurationException extends RuntimeException { | ||
|
||
private static final long serialVersionUID = 4717640118051490483L; |
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 think this is necessary or useful. No one should be using default serialization for anything, anywhere.
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 Java throws a warning which fails our build if I don't have it (agree no one should serialize it :)
warning: [serial] serializable class ConfigurationException has no definition of serialVersionUID
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.
that must be an errorprone warning. I can't imagine that javac would emit that.
switch (name) { | ||
case "otlp": | ||
case "otlp_metrics": | ||
if (metricsAlreadyRegistered) { |
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.
it's a little annoying that this same check has to be done twice, but I don't see any easy away around it without some sort of ugly nesting of switch statements
return true; | ||
default: | ||
return false; | ||
} |
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 guess you could do:
switch (name) {
case "otlp":
case "otlp_metrics":
case "prometheus":
if (metricsAlreadyRegistered) {
throw new ConfigurationException(
"Multiple metrics exporters configured. Only one metrics exporter can be "
+ "configured at a time.");
}
if ("prometheus".equals(name)) {
configurePrometheusMetrics(config, meterProvider);
return true;
}
configureOtlpMetrics(config, meterProvider);
return true;
default:
return false;
}
but I don't know if that's really much better.
SdkTracerProvider tracerProvider = | ||
TracerProviderConfiguration.configureTracerProvider(resource, exporterNames, config); | ||
|
||
if (!exporterNames.isEmpty()) { |
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 really really don't like that the previous code mutates the set and we're relying on that fact here. That was super surprising to me...in fact, I was sure this line was a bug until I dug into the implementations.
Even if it ends up being uglier, I'd very strongly prefer it if we didn't have hidden side-effects in the code like this.
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.
Yeah I mention it in a spec issue too :)
open-telemetry/opentelemetry-specification#1318 (comment)
Do you see an alternative? Should I mutate a handledExportNames
with .add
instead?
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.
You could return an updated copy of the set, maybe?
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.
It's tricky since already returning a tracerprovider from one of them. It's a bit of duplication but I went with exposing the recognized names and checking eagerly instead of mutations.
SpanExporter exporter = SpanExporterConfiguration.configureExporter(name, config); | ||
if (exporter != null) { | ||
spanExporters.add(exporter); | ||
exporterNames.remove(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.
👎
OpenTelemetrySdkAutoConfiguration.configureResource( | ||
ConfigProperties.createForTest( | ||
Collections.singletonMap("otel.resource.attributes", "telemetry.sdk.name=test"))); | ||
assertThat(resource.getAttributes().get(SemanticAttributes.TELEMETRY_SDK_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.
I didn't even realize that this was a thing that end-users could do. Is this desirable behavior?
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 we'll need it for service name? I don't think a user should do something bad but it seems to make sense semantically to prioritize them if they do go to the effort.
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 know if it's generally desirable or not, but it certainly is the way it works today, so have a test for it is good. :)
void globalOpenTelemetryWhenError() { | ||
Logger logger = Logger.getLogger(GlobalOpenTelemetry.class.getName()); | ||
AtomicReference<LogRecord> logged = new AtomicReference<>(); | ||
Handler handler = |
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.
It'll be nice to convert to the logunit code!
… into autoconfigurre
… into autoconfigurre
|
||
Set<String> unrecognizedExporters = new LinkedHashSet<>(exporterNames); | ||
unrecognizedExporters.removeAll(SpanExporterConfiguration.RECOGNIZED_NAMES); | ||
unrecognizedExporters.removeAll(MetricExporterConfiguration.RECOGNIZED_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.
this is a good solution. thanks!
Double ratio = config.getDouble("otel.trace.sampler.arg"); | ||
if (ratio == null) { | ||
throw new ConfigurationException( | ||
"otel.trace.sampler=traceidratio but otel.trace.sampler.arg is not 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.
stuff like this is a good argument for extracting the keys into constants, but that can be done as a separate step later, for sure.
} | ||
|
||
// Visible for testing | ||
@SuppressWarnings({"unchecked", "rawtypes"}) |
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 think this supression is needed any more.
...ensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ConfigProperties.java
Outdated
Show resolved
Hide resolved
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.
Thanks!
…k/autoconfigure/ConfigProperties.java Co-authored-by: John Watson <jkwatson@gmail.com>
… into autoconfigurre
This implements autoconfiguration moved into a separate layer as discussed a few times before. It's aim is to remove autoconfiguration from the core SDK so frameworks with their own configuration system, e.g., sleuth, can use it with full control, and other users can rely on our autoconfiguration if they want. Since we have control of the artifact, while slightly weird, it seems reasonable for us to automatically call into it with reflection, and otherwise we just return no-op (and log in the case of a misconfigured SDK as in #2396).
I mostly followed the parameters supported by the java agent except