Skip to content

Fix trace exporter #193

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

Merged
merged 35 commits into from
Mar 1, 2023
Merged

Fix trace exporter #193

merged 35 commits into from
Mar 1, 2023

Conversation

psx95
Copy link
Contributor

@psx95 psx95 commented Feb 15, 2023

PR to attempt fixes for #141.


These changes prevent a crash and tests verified that traces are being generated. This is an early PR for any feedback.

Startup code for testing -

private static void initializeOTel() {
		traceExporter = TraceExporter.createWithConfiguration(TraceConfiguration.builder()
				.setProjectId(PROJECT_ID)); // contained a live project

		sdkTracerProvider = SdkTracerProvider.builder()
				.setResource(resource)
				.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
				.build();

		openTelemetry = OpenTelemetrySdk.builder()
				.setTracerProvider(sdkTracerProvider)
				.buildAndRegisterGlobal();

		System.out.println("OpenTelemetry initialized Successfully !!!");
}

User Facing Changes (NONE)

The way users interact with this API has not changed, but this PR changes the internal implementation of how a TraceExporter object is initialized. The initialization is now deferred till the very first export is called.

Summary of Changes (Including implementation details) -

TraceExporter

  1. The original TraceExporter class has been converted to InternalTraceExporter - this class contains all the logic required and core logic for exporting traces is unchanged.
  2. The current TraceExporter is a wrapper around the InternalTraceExporter and delegates call to InternalTraceExporter at the appropriate time.
  3. TraceExporter holds a reference to InternalTraceExporter which gets initialized when the very first export is called. This initialization is only done once in a thread-safe manner (via memoization).

TraceConfiguration & its interaction with TraceExporter

  1. User may not provide explicit null or empty values for project ID.
  2. User provided value for the project ID is internally used in a Supplier. If the user does not call setProjectId to provide a Project ID, the generated Supplier will provide default Project ID via ServiceOptions#getDefaultProjectId. The supplier used is memoized so the value is retrieved only once.
  3. Building the TraceConfiguration object using the builder no longer attempts to fetch the defaultProjectId immediately via ServiceOptions.getDefaultProjectId() call.
  4. Building the TraceConfiguration object using builder will now generate a Supplier which will resolve to a project ID later when get() is called on it. This is done by InternalTraceExporter when it is lazy loaded.
  5. From the information in (4), this can be inferred that TraceConfiguration.getProjectId() will always return whatever user has specified as project ID.
  6. In case the call made to ServiceOptions.getDefaultProjectId() (for retrieving default project ID) fails, the TraceExporter implementation will be changed to a noop implementation and the user will be notified of this via logs.

@psx95 psx95 requested review from jsuereth, punya and dashpole February 15, 2023 20:52
@psx95 psx95 self-assigned this Feb 15, 2023
@psx95 psx95 requested a review from jsuereth February 17, 2023 18:10
@psx95 psx95 marked this pull request as ready for review February 17, 2023 18:10
@psx95 psx95 marked this pull request as draft February 17, 2023 22:56
@psx95 psx95 marked this pull request as draft February 17, 2023 22:56
@psx95 psx95 marked this pull request as draft February 17, 2023 22:56
@psx95 psx95 marked this pull request as draft February 17, 2023 22:56
Map.of("User-Agent", "opentelemetry-operations-java/" + TraceVersions.EXPORTER_VERSION);
private static final HeaderProvider HEADER_PROVIDER = () -> HEADERS;

private static InternalTraceExporter createWithClient(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this wrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So one thing to note here is that this is the exact same class as the TraceExporter in the current main.

It has just been renamed to InternalTraceExporter. In order to minimize the changes, I did not edit any existing logic.

@psx95 psx95 force-pushed the fix-exporter branch 2 times, most recently from 0e740ec to 9708c01 Compare February 19, 2023 23:21
@psx95 psx95 mentioned this pull request Feb 19, 2023
@psx95 psx95 force-pushed the fix-exporter branch 6 times, most recently from 281dd19 to 986208e Compare February 21, 2023 15:41
@psx95 psx95 marked this pull request as ready for review February 21, 2023 16:28
@psx95 psx95 requested a review from punya February 21, 2023 16:28
@psx95 psx95 marked this pull request as draft February 23, 2023 16:22
@@ -60,6 +59,16 @@ public abstract class TraceConfiguration {
.put("thread.id", "/tid")
.build();

private Supplier<String> projectIdProvider;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • An auto-value class should not have concrete fields or setters.
  • This should be named projectIdSupplier to be consistent with the type name.

One way to do this would be

  1. Remove the field
  2. Add an abstract method getProjectIdSupplier to TraceConfiguration
  3. Change the existing abstract method getProjectId to a concrete, memoized method that calls getProjectIdSupplier().get().
  4. Add a concrete method setProjectId to TraceConfiguration.Bulder that calls setProjectIdSupplier(() -> id).

}

@Override
public CompletableResultCode flush() {
// We do no exporter buffering of spans, so we're always flushed.
return CompletableResultCode.ofSuccess();
if (internalTraceExporter.get() == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we unconditionally return success for this method, without even checking whether the field has been set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, we can. But the purpose of this class it defer business logic to the internalTraceExporter.

However, I was thinking if failure is the correct code here. Any thoughts ? This condition would happen if flush is called before export somehow.

spans.add(translator.generateSpan(spanData, projectId));
public CompletableResultCode export(@Nonnull Collection<SpanData> spanDataList) {
if (Objects.isNull(internalTraceExporter.get())) {
synchronized (this) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the synchronization model here - usually an AtomicReference is used to avoid synchronization. You might be able to avoid a lot of this complexity by changing internalTraceExporter to Suppliers.memoized(() -> new InternalTraceExporter(this.config)) and making it do all the work.

projectId, cloudTraceClient, attributeMappings, fixedAttributes);
}

static SpanExporter createWithConfiguration(TraceConfiguration configuration) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider wrapping the IOException at this layer and removing it from the signature. Since our external API no longer has an IOException, this just adds noise to our implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this exception may be thrown from two possible places, handling it at the InternalTraceExporter level makes the implementation a little ugly since both need to be handled. Also, InternalTraceExporter is the class that actually has the implementation so handling it there would introduce more noise to our implementation detail.

I think it makes sense to keep it in the function signature here since it has a package private access and this exception is handled in TraceExporter for both cases.

@psx95 psx95 marked this pull request as draft February 24, 2023 16:49
@psx95 psx95 marked this pull request as ready for review February 27, 2023 16:04
@psx95 psx95 requested a review from punya February 27, 2023 17:15
Copy link
Contributor

@punya punya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


private TraceExporter(TraceConfiguration configuration) {
this.internalTraceExporterSupplier =
Suppliers.memoize(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of the memoize function.

Copy link
Collaborator

@jsuereth jsuereth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Noop version should not be returning any failures.

@psx95 psx95 requested a review from jsuereth March 1, 2023 17:31
@psx95 psx95 changed the title Fix exporter Fix trace exporter Mar 1, 2023
@jsuereth jsuereth merged commit 70e3b08 into GoogleCloudPlatform:main Mar 1, 2023
@psx95 psx95 deleted the fix-exporter branch April 29, 2024 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants