Repository navigation
Distributed tracing support with Micrometer and OpenTelemetry #376
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
Changes from 5 commits
98ad35e
01e7620
af9e289
8fae4d3
2a2f309
2d5d1b5
fda47dc
56e34d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package org.lognet.springboot.grpc.tracing; | ||
|
|
||
| import io.grpc.CallOptions; | ||
| import io.grpc.Channel; | ||
| import io.grpc.ClientCall; | ||
| import io.grpc.ClientInterceptor; | ||
| import io.grpc.ForwardingClientCall; | ||
| import io.grpc.Metadata; | ||
| import io.grpc.MethodDescriptor; | ||
| import io.opentelemetry.api.trace.Span; | ||
| import io.opentelemetry.api.trace.TraceId; | ||
|
|
||
| public class TracingClientInterceptor implements ClientInterceptor { | ||
| @Override | ||
| public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( | ||
| MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { | ||
|
|
||
| return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) { | ||
| @Override | ||
| public void start(ClientCall.Listener<RespT> responseListener, Metadata headers) { | ||
| Span currentSpan = Span.current(); | ||
| String traceId = currentSpan.getSpanContext().getTraceId(); | ||
| String spanId = currentSpan.getSpanContext().getSpanId(); | ||
|
|
||
| if (!traceId.equals(TraceId.getInvalid())) { | ||
| headers.put(Metadata.Key.of("traceId", Metadata.ASCII_STRING_MARSHALLER), traceId); | ||
| headers.put(Metadata.Key.of("spanId", Metadata.ASCII_STRING_MARSHALLER), spanId); | ||
| } | ||
| super.start(responseListener, headers); | ||
| } | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package org.lognet.springboot.grpc.autoconfigure.tracing; | ||
|
|
||
| import io.opentelemetry.api.trace.Tracer; | ||
| import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; | ||
| import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; | ||
| import io.opentelemetry.sdk.OpenTelemetrySdk; | ||
| import io.opentelemetry.sdk.trace.SdkTracerProvider; | ||
| import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; | ||
| import org.lognet.springboot.grpc.tracing.TracingServerInterceptor; | ||
| import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpProperties; | ||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| @EnableConfigurationProperties(OtlpProperties.class) | ||
| public class OtelConfiguration { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be conditional on
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense |
||
|
|
||
| @Bean | ||
| public OtlpGrpcSpanExporter otlpExporter(OtlpProperties properties) { | ||
| OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder().setEndpoint(properties.getEndpoint()); | ||
| return builder.build(); | ||
| } | ||
|
|
||
| @Bean | ||
| public Tracer tracer(OtlpGrpcSpanExporter otlpExporter) { | ||
| SdkTracerProvider tracerProvider = SdkTracerProvider.builder() | ||
| .addSpanProcessor(BatchSpanProcessor.builder(otlpExporter).build()) | ||
| .build(); | ||
|
|
||
| OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder() | ||
| .setTracerProvider(tracerProvider) | ||
| .buildAndRegisterGlobal(); | ||
|
|
||
| return openTelemetry.getTracerProvider().get("grpc-spring-boot-starter"); | ||
| } | ||
|
|
||
| @Bean | ||
| public TracingServerInterceptor tracingServerInterceptor(Tracer tracer) { | ||
| return new TracingServerInterceptor(tracer); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package org.lognet.springboot.grpc.log; | ||
|
|
||
| import io.grpc.Metadata; | ||
| import io.grpc.ServerCall; | ||
| import io.grpc.ServerCallHandler; | ||
| import io.grpc.ServerInterceptor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class LogInterceptor implements ServerInterceptor { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an example but LogInterceptor was not a part of grpc-spring-boot-starter. I can add it in a separate PR if needed
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this class needed ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just wanted to use it from the library. Not sure why it doesn't exist. Removed |
||
|
|
||
| private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class); | ||
|
|
||
| @Override | ||
| public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( | ||
| ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { | ||
| logger.info(call.getMethodDescriptor().getFullMethodName()); | ||
| return next.startCall(call, headers); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package org.lognet.springboot.grpc.tracing; | ||
|
|
||
| import io.grpc.Metadata; | ||
| import io.grpc.ServerCall; | ||
| import io.grpc.ServerCallHandler; | ||
| import io.grpc.ServerInterceptor; | ||
| import io.opentelemetry.api.trace.Span; | ||
| import io.opentelemetry.api.trace.SpanBuilder; | ||
| import io.opentelemetry.api.trace.SpanContext; | ||
| import io.opentelemetry.api.trace.TraceFlags; | ||
| import io.opentelemetry.api.trace.TraceState; | ||
| import io.opentelemetry.api.trace.Tracer; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.Scope; | ||
|
|
||
| public class TracingServerInterceptor implements ServerInterceptor { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should have the highest precedence https://github.com/LogNet/grpc-spring-boot-starter#421-interceptors-ordering
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added @order(20)? I don't see you use
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see this, we will need to move
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've set HIGHEST_PRECEDENCE. Do I need to touch recovery and auth here? Please, let me know if you want to change order somehow |
||
|
|
||
| private final Tracer tracer; | ||
|
|
||
| public TracingServerInterceptor(Tracer tracer) { | ||
| this.tracer = tracer; | ||
| } | ||
|
|
||
| @Override | ||
| public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( | ||
| ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { | ||
|
|
||
| String traceId = headers.get(Metadata.Key.of("traceId", Metadata.ASCII_STRING_MARSHALLER)); | ||
| String spanId = headers.get(Metadata.Key.of("spanId", Metadata.ASCII_STRING_MARSHALLER)); | ||
|
|
||
| Context spanContext = createSpanContext(traceId, spanId); | ||
| SpanBuilder spanBuilder = tracer.spanBuilder("grpc-spring-boot-starter-span").setParent(spanContext); | ||
| Span span = spanBuilder.startSpan(); | ||
| Context scopedContext = Context.current().with(span); | ||
|
|
||
| try (Scope scope = scopedContext.makeCurrent()) { | ||
| return next.startCall(call, headers); | ||
| } finally { | ||
| span.end(); | ||
| } | ||
| } | ||
|
|
||
| private Context createSpanContext(String traceId, String spanId) { | ||
| SpanContext spanContext = SpanContext.createFromRemoteParent( | ||
| traceId != null ? traceId : "", | ||
| spanId != null ? spanId : "", | ||
| TraceFlags.getDefault(), | ||
| TraceState.getDefault() | ||
| ); | ||
| return Context.current().with(Span.wrap(spanContext)); | ||
| } | ||
| } | ||
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.
io.micrometer:micrometer-tracing-bridge-otelis it in-use ?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.