Skip to content

Commit

Permalink
feat: read the TraceId from Header and propagate it
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardostaffolani committed Jul 17, 2024
1 parent 610d886 commit 7be440e
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 7 deletions.
48 changes: 48 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<opensaml-version>2.6.6</opensaml-version>
<spring-security-saml2-core-version>1.0.10.RELEASE</spring-security-saml2-core-version>
<velocity-version>2.3</velocity-version>
<spring-cloud.version>2021.0.5</spring-cloud.version>


<start-class>ch.bfh.ti.i4mi.mag.MobileAccessGateway</start-class>
Expand Down Expand Up @@ -69,6 +70,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- W3C TraceId -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down Expand Up @@ -152,6 +161,19 @@
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>

<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-servlet-starter</artifactId>
Expand Down Expand Up @@ -322,6 +344,32 @@
<scope>test</scope>
</dependency>

<!-- W3C TraceId -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
<version>3.1.5</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-brave</artifactId>
<version>3.1.5</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>

<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

</dependencies>

<build>
Expand Down
73 changes: 73 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/CustomPropagationFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ch.bfh.ti.i4mi.mag;

import brave.propagation.B3Propagation;
import brave.propagation.Propagation;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;

import java.util.Arrays;
import java.util.List;

public class CustomPropagationFactory extends Propagation.Factory {

private static final String TRACE_PARENT = "traceparent";
private static final String TRACE_STATE = "tracestate";

@Override
public <K> Propagation<K> create(Propagation.KeyFactory<K> keyFactory) {
return new CustomPropagation<>(B3Propagation.FACTORY.create(keyFactory), keyFactory);
}

static final class CustomPropagation<K> implements Propagation<K> {
final Propagation<K> b3;
final K traceParentKey, traceStateKey;

CustomPropagation(Propagation<K> b3, KeyFactory<K> keyFactory) {
this.b3 = b3;
this.traceParentKey = keyFactory.create(TRACE_PARENT);
this.traceStateKey = keyFactory.create(TRACE_STATE);
}

@Override
public List<K> keys() {
return Arrays.asList(traceParentKey, traceStateKey);
}

@Override
public <C> TraceContext.Injector<C> injector(Setter<C, K> setter) {
return b3.injector(setter);
}

@Override
public <C> TraceContext.Extractor<C> extractor(Getter<C, K> getter) {
return new TraceContext.Extractor<C>() {
@Override
public TraceContextOrSamplingFlags extract(C carrier) {
String traceParent = getter.get(carrier, traceParentKey);
if (traceParent != null) {
return extractFromTraceParent(traceParent);
}
return b3.extractor(getter).extract(carrier);
}
};
}

private TraceContextOrSamplingFlags extractFromTraceParent(String traceParent) {
String[] parts = traceParent.split("-");
if (parts.length != 4) {
return TraceContextOrSamplingFlags.EMPTY;
}
long traceIdHigh = Long.parseUnsignedLong(parts[1].substring(0, 16), 16);
long traceIdLow = Long.parseUnsignedLong(parts[1].substring(16), 16);
long spanId = Long.parseUnsignedLong(parts[2], 16);
byte samplingFlags = Byte.parseByte(parts[3], 16);

return TraceContextOrSamplingFlags.create(TraceContext.newBuilder()
.traceIdHigh(traceIdHigh)
.traceId(traceIdLow)
.spanId(spanId)
.sampled(samplingFlags == 1)
.build());
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/FilterConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ch.bfh.ti.i4mi.mag;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean<TraceHeaderLoggingFilter> loggingFilter() {
FilterRegistrationBean<TraceHeaderLoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TraceHeaderLoggingFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}

@Bean
public FilterRegistrationBean<RequestIdFilter> requestIdFilter() {
FilterRegistrationBean<RequestIdFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RequestIdFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(2);
return registrationBean;
}
}
30 changes: 30 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/LoggingSpanHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ch.bfh.ti.i4mi.mag;

import brave.handler.MutableSpan;
import brave.handler.SpanHandler;
import brave.propagation.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingSpanHandler extends SpanHandler {
private static final Logger logger = LoggerFactory.getLogger(LoggingSpanHandler.class);

@Override
public boolean begin(TraceContext context, MutableSpan span, TraceContext parent) {
logger.info("Span started: traceId={}, spanId={}, parentId={}",
context.traceIdString(),
context.spanIdString(),
parent != null ? parent.spanIdString() : "none");
return true;
}

@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
logger.info("Span completed: traceId={}, spanId={}, name={}, duration={}ms",
context.traceIdString(),
context.spanIdString(),
span.name(),
span.finishTimestamp() - span.startTimestamp());
return true;
}
}
4 changes: 2 additions & 2 deletions src/main/java/ch/bfh/ti/i4mi/mag/MobileAccessGateway.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
*
* @author Oliver Egger
*/
@SpringBootApplication
@SpringBootApplication(exclude = {org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration.class})
@Slf4j
@ComponentScan(basePackages={"ch.bfh.ti.i4mi.mag","org.openehealth.ipf","org.springframework.security.saml"})
@ComponentScan(basePackages={"ch.bfh.ti.i4mi.mag","org.openehealth.ipf","org.springframework.security.saml"})
// without it does not work directly with mvn and current snapshot, when running the Pixm query an error is returned "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "processing", "diagnostics": "Unknown resource type 'Patient' - Server knows how to handle: [StructureDefinition, OperationDefinition]" } ]
// it looks like the META-INF directory is not correct configured that is copied to the output, if it is added in eclipse as on open project to java/main/resources it works without above line
@EnableAutoConfiguration
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/RequestIdFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ch.bfh.ti.i4mi.mag;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

public class RequestIdFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(RequestIdFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
try {
logger.info("Processing request: {}", requestId);
filterChain.doFilter(request, response);
} finally {
logger.info("Completed request: {}", requestId);
MDC.remove("requestId");
}
}
}
50 changes: 50 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/TraceHeaderLoggingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch.bfh.ti.i4mi.mag;

import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

@Component
public class TraceHeaderLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(TraceHeaderLoggingFilter.class);

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

String traceparent = request.getHeader("traceparent");
String tracestate = request.getHeader("tracestate");
String b3 = request.getHeader("b3");
String b3TraceId = request.getHeader("X-B3-TraceId");
String b3SpanId = request.getHeader("X-B3-SpanId");

// Log the headers in a structured format
logger.info("Trace headers: traceparent={}, tracestate={}, b3={}, X-B3-TraceId={}, X-B3-SpanId={}",
traceparent, tracestate, b3, b3TraceId, b3SpanId);

// Add the headers to MDC for logging
MDC.put("traceparent", traceparent);
MDC.put("tracestate", tracestate);
MDC.put("b3", b3);
MDC.put("X-B3-TraceId", b3TraceId);
MDC.put("X-B3-SpanId", b3SpanId);

try {
filterChain.doFilter(request, response);
} finally {
// Clean up MDC
MDC.clear(); // This removes all MDC entries
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/ch/bfh/ti/i4mi/mag/TracingConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ch.bfh.ti.i4mi.mag;

import brave.Tracing;
import brave.propagation.B3Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.Propagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.sampler.Sampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TracingConfiguration {

@Bean
public Tracing tracing() {
return Tracing.newBuilder()
.sampler(Sampler.ALWAYS_SAMPLE)
.propagationFactory(new CustomPropagationFactory())
.currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder().build())
.supportsJoin(false)
.traceId128Bit(true)
.build();
}

@Bean
public brave.handler.SpanHandler loggingSpanHandler() {
return new LoggingSpanHandler();
}
}
Loading

0 comments on commit 7be440e

Please sign in to comment.