Skip to content
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 support for OpenTelemetry #65

Merged
merged 45 commits into from
May 26, 2022

Conversation

n0tl3ss
Copy link
Member

@n0tl3ss n0tl3ss commented Apr 6, 2022

This PR adds OpenTelemetry in the micronaut-tracing module.

Modules

Five new modules are added:

  • micronaut-tracing-annotation - Annotation that are used both inside the Open Telemetry and Open Tracing.
  • micronaut-tracing-opentelemetry -Adds Open Telemetry default instrumentation and uses Open Telemetry Auto configure SDK.
  • micronaut-tracing-opentelemetry-annotation - Mapper and Transformer for Open Telemetry @WithSpan and @SpanAttribute annotations.
  • micronaut-tracing-opentelemetry-http - Open Telemetry Instrumentation for HTTP Client and Server.
  • micronaut-tracing-opentelemetry-grpc - Open Telemetry Instrumentation for GRPC Client and Server.

Supported Features

  • Starting Span objects on the client/server HTTP requests and ending them on the client/server HTTP response.
  • Starting Span objects on the client/server GRPC requests and ending them on the client/server GRPC response.
  • The Open Telemetry @WithSpan and @SpanAttribute annotations are fully supported. The @SpanTag , @NewSpan, @ContinueSpan from the Open Tracing are also usable.
  • Adding custom headers inside Span Objects defined inside application.yml file
  • Open Telemetry Auto configure SDK config variables can also be defined inside application.yml.
  • Defaults that are defined inside this module: otel.traces.exporter = none, otel.metrics.exporter = none, otel.logs.exporte = none, otel.service.name = value of the application.name. This values can be overridden through application.yml file.

Other

  • Added tests that covers OpenTelemetry integration and tests old and new annotations.
  • Usage is described inside module documentation.

@n0tl3ss n0tl3ss requested a review from graemerocher April 6, 2022 10:06
@donbeave
Copy link
Contributor

donbeave commented Apr 9, 2022

Maybe it's better to add additional modules to initialize different OTEL standalone instrumentations. For example, if you want to active OTEL for gRPC, you should add micronaut-tracing-opentelemetry-grpc-server, which will add the official library io.opentelemetry.instrumentation:opentelemetry-grpc-1.6 library to the classpath and initialize grpc ServerInterceptor, for client side support should add micronaut-tracing-opentelemetry-grpc-client. With this approach, it will reuse official libraries and will not add extra unused dependencies to the classpath.

With current Micronaut features ecosystem I can see a list of such modules:

  1. micronaut-tracing-opentelemetry-aws-sdk
  2. micronaut-tracing-opentelemetry-grpc-server
  3. micronaut-tracing-opentelemetry-grpc-client
  4. micronaut-tracing-opentelemetry-graphql
  5. micronaut-tracing-opentelemetry-jdbc
  6. micronaut-tracing-opentelemetry-lettuce
  7. micronaut-tracing-opentelemetry-log4j
  8. micronaut-tracing-opentelemetry-logback
  9. micronaut-tracing-opentelemetry-mongo
  10. micronaut-tracing-opentelemetry-reactor
  11. micronaut-tracing-opentelemetry-rxjava1
  12. micronaut-tracing-opentelemetry-rxjava2
  13. micronaut-tracing-opentelemetry-rxjava3

* @since 1.0
*/
@ConfigurationProperties(PREFIX)
public class DefaultConfiguration implements Toggleable {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename this to something more specific. DefaultConfiguration will too generic a name.
Member

I would probably also use an interface here as it will be simpler:

@ConfigurationProperties(PREFIX)
interface TracingConfiguration extends Toggleable {
    String PREFIX = "tracing";
}

*/
@Singleton
@Primary
OpenTelemetry defaultOpenTelemetry() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably worth making protected if the intention is to allow the user to subclass and override

public class DefaultTelemetryTracer {

@Property(name = APPLICATION_NAME)
String applicationName;
Copy link
Contributor

Choose a reason for hiding this comment

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

Try not to use field injection. Better to make this final and use constructor injection making the type immutable.

} else {
// must be new
String operationName = newSpan.stringValue().orElse(null);
Optional<String> hystrixCommand = context.stringValue(HYSTRIX_ANNOTATION);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can remove this logic, hystrix is deprecated

Comment on lines 212 to 217
context.stringValue(HYSTRIX_ANNOTATION, "group").ifPresent(s ->
span.setAttribute(TAG_HYSTRIX_GROUP, s)
);
context.stringValue(HYSTRIX_ANNOTATION, "threadPool").ifPresent(s ->
span.setAttribute(TAG_HYSTRIX_THREAD_POOL, s)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

you can remove this hystrix is deprecated

}

try (Scope ignored = span.makeCurrent()) {
publisher.subscribe(new Subscriber<T>() {
Copy link
Contributor

Choose a reason for hiding this comment

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

needs to implement CoreSubscriber from reactor for propagation to work

* The tracing subscriber.
*/
@Internal
protected class TracingSubscriber implements Subscriber<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

needs to implement CoreSubscriber

Copy link
Contributor

@donbeave donbeave left a comment

Choose a reason for hiding this comment

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

* @author Nemanja Mikic
* @since 1.0
*/
public abstract class AbstractOpenTracingFilter implements HttpFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest don't use a custom implementation of OTEL instrumenter, we can reuse the official library here. The benefit of this approach is that Micronaut traces will have the same format as the official OTEL implementation. For those companies who combine different frameworks and run multiple applications, some with OTEL javaagent they will not see differences in OTEL spans created in Micronaut.
Please check my comments for OpenTracingClientFilter and OpenTracingServerFilter, which provide links to examples.

*/
@Filter(CLIENT_PATH)
@Requires(beans = Tracer.class)
public class OpenTelemetryClientFilter extends AbstractOpenTracingFilter implements HttpClientFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's better to use the official opentelemetry-instrumentation-api library here, which provides API to simplify creating new instrumentation. You can check my example which uses this library: https://github.com/tailrocks/micronaut-opentelemetry/blob/7f1e83e70e023375b3fe6684a8c273fc649d1682/opentelemetry/src/main/java/io/micronaut/opentelemetry/instrumentation/http/client/OpenTelemetryClientFilter.java#L41

If you need some explanation why it's much better than re-implement everything from scratch, please tag me and I will provide some examples.

*/
@Filter(SERVER_PATH)
@Requires(beans = Tracer.class)
public class OpenTelemetryServerFilter extends AbstractOpenTracingFilter implements HttpServerFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

@n0tl3ss n0tl3ss requested a review from graemerocher April 15, 2022 17:29
Copy link
Contributor

@donbeave donbeave left a comment

Choose a reason for hiding this comment

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

I'll try this branch in some of my Micronaut applications and provide you with some feedback.

dependencies {
api mn.micronaut.runtime
api mn.micronaut.http.client
api mn.micronaut.grpc.server.runtime
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we better split it into modules or add all initializers into one module but activate only if the class is provided in the classpath. For example to activate gRPC interceptors you should add:

implementation("io.opentelemetry.instrumentation:opentelemetry-grpc-1.6")

Copy link
Contributor

Choose a reason for hiding this comment

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

Same for Micronaut HTTP client as well.

* @author Nemanja Mikic
*/
@Factory
public class GrpcClientTracingInterceptorFactory {
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -16,7 +18,14 @@ opentracing = { module = 'io.opentracing:opentracing-api', version.ref = 'opentr
opentracing-util = { module = 'io.opentracing:opentracing-util', version.ref = 'opentracing' }
zipkin-brave-instrumentation = { module = 'io.zipkin.brave:brave-instrumentation-http', version.ref = 'brave-instrumentation' }
zipkin-reporter = { module = 'io.zipkin.reporter2:zipkin-reporter', version.ref = 'zipkin-reporter' }

opentelemetry-api = {module='io.opentelemetry:opentelemetry-api', version.ref ='opentelemetry'}
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems it's not formatted as usual. Please apply the same format.

opentelemetry-sdk-logs = {module='io.opentelemetry:opentelemetry-sdk-logs', version.ref = 'opentelemetry-alpha'}
opentelemetry-extension-annotations = {module='io.opentelemetry:opentelemetry-extension-annotations', version.ref = 'opentelemetry'}
opentelemetry-instrumentation-api = {module='io.opentelemetry.instrumentation:opentelemetry-instrumentation-api', version.ref = 'opentelemetry-alpha'}
opentelemetry-instrumentation-grpc = {module='io.opentelemetry.instrumentation:opentelemetry-grpc-1.6', version.ref = 'opentelemetry-alpha'}
[plugins]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a new line before [plugins]?

@n0tl3ss
Copy link
Member Author

n0tl3ss commented Apr 18, 2022

@donbeave I will try to add grpc tests. Do you want me to create tracing-opentelemetry-grpc module and move everything related to the grpc there?

With test I have an issue with generating protobuff classes.

Here is an error:


An exception occurred applying plugin request [id: 'io.micronaut.build.internal.docs']
> Failed to apply plugin class 'io.micronaut.build.docs.JavadocAggregatorPlugin'.
   > A problem occurred configuring project ':tracing-bom'.
      > A problem occurred configuring project ':tracing-opentelemetry'.
         > A problem occurred evaluating project ':tracing-opentelemetry'.
            > Could not find method protobuf() for arguments [build_96ccfw8zuqrkx91x0900im2yo$_run_closure3@1472beea] on project ':tracing-opentelemetry' of type org.gradle.api.Project.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

@n0tl3ss
Copy link
Member Author

n0tl3ss commented Apr 18, 2022

@donbeave I will try to add grpc tests. Do you want me to create tracing-opentelemetry-grpc module and move everything related to the grpc there?

Also this test io.micronaut.tracing.instrument.util.AnnotationMappingSpec test map WithSpan annotation is falling somehow on CI/CD but locally its passes: https://ge.micronaut.io/s/hcasuab4pcl2k

@donbeave
Copy link
Contributor

Do you want me to create tracing-opentelemetry-grpc module and move everything related to the grpc there?

I'm not sure which way is better, maybe @graemerocher can give us some feedback.

I see two approaches

  1. separate plugin for each stack, like micronaut-tracing-opentelemetry-http-client, micronaut-tracing-opentelemetry-http-server, micronaut-tracing-opentelemetry-grpc, micronaut-tracing-opentelemetry-graphql, etc.

or

  1. put everything in micronaut-tracing-opentelemetry, but activates only if related classes are provided in the classpath, for example to activates gRPC user should add implementation("io.opentelemetry.instrumentation:opentelemetry-grpc-1.6"), to activate GraphQL user should add implementation("io.opentelemetry.instrumentation:opentelemetry-graphql-java-12.0"), etc

@Override
public List<AnnotationValue<?>> map(AnnotationValue<WithSpan> annotation, VisitorContext visitorContext) {
AnnotationValue<InterceptorBinding> interceptorBinding = AnnotationValue.builder(InterceptorBinding.class)
.value(ContinueSpan.class)
Copy link
Contributor

@donbeave donbeave Apr 18, 2022

Choose a reason for hiding this comment

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

Looks like it's not correct to map WithSpan to ContinueSpan annotation, it should be mapped to NewSpan annotation instead, based on its description from javadoc:

This annotation marks that an execution of this method or constructor should result in a new io.opentelemetry.api.trace.Span.
Application developers can use this annotation to signal OpenTelemetry auto-instrumentation that a new span should be created whenever marked method is executed.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have changed WithSpan to match the NewSpan. Grpc test added, and I don't know how to fix test that is falling on ci / cd because localy it is passing: https://ge.micronaut.io/s/53d2qu5xuicqy. The issue is in mapping/transforming annotations.

@Factory
public class DefaultOpenTelemetryFactory {

private static final String SERVICE_NAME_KEY = "otel.service.name";
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 have opentelemetry. instead of otel.? WDYT @graemerocher
I would also have a constant PREFIX.

Copy link
Member Author

Choose a reason for hiding this comment

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

The "otel" prefix comes from https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md and this factory overrides some defaults of this library.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, I think if we want to reuse the original official OpenTelemetry configuration properties we should use otel as well. Maybe it will be nice to support the configuration of most of these properties directly in the application.yml file.

Copy link
Member Author

Choose a reason for hiding this comment

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

@donbeave We already support most of linked properties through application.yml

Copy link
Contributor

Choose a reason for hiding this comment

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

Fine with me

@graemerocher graemerocher removed the request for review from burtbeckwith May 23, 2022 13:41
@graemerocher graemerocher changed the title Feature/opentelemetry Add support for OpenTelemetry May 23, 2022
@n0tl3ss n0tl3ss requested review from graemerocher and dstepanov May 24, 2022 13:27
@n0tl3ss
Copy link
Member Author

n0tl3ss commented May 24, 2022

I tried to apply all of your suggestions @graemerocher @dstepanov. Can you please double check everything?

@n0tl3ss n0tl3ss requested a review from burtbeckwith May 24, 2022 14:22
@Factory
public class DefaultOpenTelemetryFactory {

private static final String SERVICE_NAME_KEY = "otel.service.name";
Copy link
Contributor

Choose a reason for hiding this comment

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

Fine with me

@n0tl3ss n0tl3ss requested review from dstepanov and graemerocher May 25, 2022 11:53
opentracing = '0.33.0'
managed-opentracing-grpc = '0.2.3'
managed-protobuf = '0.8.17'
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this does not need to be managed.


/**
* Implements tracing logic for <code>ContinueSpan</code> and <code>NewSpan</code>
* using the Open Tracing API.
Copy link
Contributor

Choose a reason for hiding this comment

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

Open Tracing API.

Is it a typo? Correct is OpenTelemetry API, right?

@graemerocher graemerocher marked this pull request as ready for review May 25, 2022 12:50
@graemerocher
Copy link
Contributor

If nobody has any further objections this looks like a good initial start to me

Copy link
Member

@burtbeckwith burtbeckwith left a comment

Choose a reason for hiding this comment

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

:shipit:

@graemerocher graemerocher merged commit 1d3545f into micronaut-projects:master May 26, 2022
@graemerocher
Copy link
Contributor

if folks would like to try it out before final release a 4.2.0-SNAPSHOT version of this module is available on the snapshot repo. See https://docs.micronaut.io/latest/guide/#usingsnapshots

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.

9 participants