diff --git a/modules/logging/pom.xml b/modules/logging/pom.xml index df0861a6..0f0484c6 100644 --- a/modules/logging/pom.xml +++ b/modules/logging/pom.xml @@ -22,7 +22,7 @@ net.logstash.logback logstash-logback-encoder - + org.slf4j jcl-over-slf4j @@ -59,6 +59,10 @@ org.springframework.boot spring-boot-starter + \ No newline at end of file diff --git a/modules/logging/src/main/java/com/devonfw/module/logging/common/api/DiagnosticContextFacade.java b/modules/logging/src/main/java/com/devonfw/module/logging/common/api/DiagnosticContextFacade.java index c3aa316f..5fcb0010 100644 --- a/modules/logging/src/main/java/com/devonfw/module/logging/common/api/DiagnosticContextFacade.java +++ b/modules/logging/src/main/java/com/devonfw/module/logging/common/api/DiagnosticContextFacade.java @@ -30,4 +30,36 @@ public interface DiagnosticContextFacade { */ void removeCorrelationId(); + // /** + // * + // */ + // void removeTraceId(); + // + // /** + // * + // */ + // void removeSpanId(); + // + // /** + // * + // * @return + // */ + // String getTraceId(); + // + // /** + // * + // * @return + // */ + // String getSpanId(); + // + // /** + // * @param spanId + // */ + // void setSpanId(String spanId); + // + // /** + // * @param traceId + // */ + // void setTraceId(String traceId); + } diff --git a/modules/logging/src/main/java/com/devonfw/module/logging/common/api/LoggingConstants.java b/modules/logging/src/main/java/com/devonfw/module/logging/common/api/LoggingConstants.java index 660e1b8e..88aede23 100644 --- a/modules/logging/src/main/java/com/devonfw/module/logging/common/api/LoggingConstants.java +++ b/modules/logging/src/main/java/com/devonfw/module/logging/common/api/LoggingConstants.java @@ -15,6 +15,26 @@ public final class LoggingConstants { */ public static final String CORRELATION_ID = "correlationId"; + /** + * SPAN ID + */ + public static final String SPAN_ID = "spanId"; + + /** + * TRACEID + */ + public static final String TRACE_ID = "traceId"; + + /** + * SPANNAME + */ + public static final String SPAN_NAME = "spanName"; + + /** + * PARENT_ID + */ + public static final String PARENT_ID = "parentSpanId"; + /** * Construction prohibited. */ diff --git a/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/DiagnosticContextFacadeImpl.java b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/DiagnosticContextFacadeImpl.java index 88f407de..60db77b6 100644 --- a/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/DiagnosticContextFacadeImpl.java +++ b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/DiagnosticContextFacadeImpl.java @@ -37,4 +37,43 @@ public void removeCorrelationId() { MDC.remove(LoggingConstants.CORRELATION_ID); } + // @Override + // public void setTraceId(String traceId) { + // + // MDC.put(LoggingConstants.TRACE_ID, traceId); + // + // } + // + // @Override + // public void setSpanId(String spanId) { + // + // MDC.put(LoggingConstants.SPAN_ID, spanId); + // } + // + // @Override + // public void removeTraceId() { + // + // MDC.remove(LoggingConstants.TRACE_ID); + // + // } + // + // @Override + // public void removeSpanId() { + // + // MDC.remove(LoggingConstants.SPAN_ID); + // + // } + // + // @Override + // public String getTraceId() { + // + // return MDC.get(LoggingConstants.TRACE_ID); + // } + // + // @Override + // public String getSpanId() { + // + // return MDC.get(LoggingConstants.SPAN_ID); + // } + } diff --git a/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceContextFilter.java b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceContextFilter.java new file mode 100644 index 00000000..51add307 --- /dev/null +++ b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceContextFilter.java @@ -0,0 +1,235 @@ +// package com.devonfw.module.logging.common.impl; +// +// import java.io.IOException; +// +// import javax.inject.Inject; +// import javax.servlet.Filter; +// import javax.servlet.FilterChain; +// import javax.servlet.FilterConfig; +// import javax.servlet.ServletContext; +// import javax.servlet.ServletException; +// import javax.servlet.ServletRequest; +// import javax.servlet.ServletResponse; +// import javax.servlet.http.HttpServletRequest; +// +// import org.slf4j.Logger; +// import org.slf4j.LoggerFactory; +// +// import com.devonfw.module.logging.common.api.DiagnosticContextFacade; +// import com.devonfw.module.logging.common.api.LoggingConstants; +// +// import brave.Tracer; +// import brave.propagation.TraceContext; +// +/// ** +// * +// */ +// public class TraceContextFilter implements Filter { +// +// private static final Logger LOG = LoggerFactory.getLogger(TraceContextFilter.class); +// +// /** +// * +// */ +// public static final String TRACE_ID_HEADER_NAME_PARAM = "traceIdHeaderName"; +// +// /** +// * +// */ +// public static final String TRACE_ID_HEADER_NAME_DEFAULT = "X-Trace-Id"; +// +// /** +// * +// */ +// public static final String SPAN_ID_HEADER_NAME_PARAM = "spanIdHeaderName"; +// +// /** +// * +// */ +// public static final String SPAN_ID_HEADER_NAME_DEFAULT = "X-Span-Id"; +// +// /** @see #setTraceIdHttpHeaderName(String) */ +// private String traceIdHttpHeaderName; +// +// /** @see #setSpanIdHttpHeaderName(String) */ +// private String spanIdHttpHeaderName; +// +// private DiagnosticContextFacade diagnosticContextFacade; +// +// private TraceHeadersInjector traceHeadersInjector; +// +// @Inject +// private Tracer tracer; +// +// /** +// * The constructor. +// */ +// public TraceContextFilter() { +// +// super(); +// this.traceIdHttpHeaderName = TRACE_ID_HEADER_NAME_DEFAULT; +// this.spanIdHttpHeaderName = SPAN_ID_HEADER_NAME_DEFAULT; +// } +// +// /** +// * @param traceIdHttpHeaderName +// */ +// public void setTraceIdHttpHeaderName(String traceIdHttpHeaderName) { +// +// this.traceIdHttpHeaderName = traceIdHttpHeaderName; +// } +// +// /** +// * @param spanIdHeaderName +// */ +// public void setSpanIdHeaderName(String spanIdHeaderName) { +// +// this.spanIdHttpHeaderName = spanIdHeaderName; +// } +// +// private static String normalizeValue(String value) { +// +// if (value != null) { +// String result = value.trim(); +// if (!result.isEmpty()) { +// return result; +// } +// } +// return null; +// } +// +// @Override +// public void init(FilterConfig filterConfig) throws ServletException { +// +// String traceHeaderName = filterConfig.getInitParameter(TRACE_ID_HEADER_NAME_PARAM); +// String spanHeaderName = filterConfig.getInitParameter(SPAN_ID_HEADER_NAME_PARAM); +// +// if (traceHeaderName == null) { +// LOG.debug("Parameter {} not configured via filter config.", TRACE_ID_HEADER_NAME_PARAM); +// } else { +// this.traceIdHttpHeaderName = traceHeaderName; +// } +// +// if (spanHeaderName == null) { +// LOG.debug("Parameter {} not configured via filter config.", SPAN_ID_HEADER_NAME_PARAM); +// } else { +// this.spanIdHttpHeaderName = spanHeaderName; +// } +// +// LOG.info("trace ID header initialized to: {}", this.traceIdHttpHeaderName); +// LOG.info("span ID header initialized to: {}", this.spanIdHttpHeaderName); +// +// if (this.diagnosticContextFacade == null) { +// try { +// // ATTENTION: We do not import these classes as we keep spring as an optional dependency. +// // If spring is not available in your classpath (e.g. some real JEE context) then this will produce a +// // ClassNotFoundException and use the fallback in the catch statement. +// ServletContext servletContext = filterConfig.getServletContext(); +// org.springframework.web.context.WebApplicationContext springContext; +// springContext = org.springframework.web.context.support.WebApplicationContextUtils +// .getWebApplicationContext(servletContext); +// this.diagnosticContextFacade = springContext.getBean(DiagnosticContextFacade.class); +// } catch (Throwable e) { +// LOG.warn("DiagnosticContextFacade not defined in spring. Falling back to default", e); +// this.diagnosticContextFacade = new DiagnosticContextFacadeImpl(); +// } +// +// } +// +// if (this.traceHeadersInjector == null) { +// try { +// // ATTENTION: We do not import these classes as we keep spring as an optional dependency. +// // If spring is not available in your classpath (e.g. some real JEE context) then this will produce a +// // ClassNotFoundException and use the fallback in the catch statement. +// ServletContext servletContext = filterConfig.getServletContext(); +// org.springframework.web.context.WebApplicationContext springContext; +// springContext = org.springframework.web.context.support.WebApplicationContextUtils +// .getWebApplicationContext(servletContext); +// this.traceHeadersInjector = springContext.getBean(TraceHeadersInjector.class); +// } catch (Throwable e) { +// LOG.warn("TraceHeadersInjector not defined in spring. Falling back to default", e); +// this.traceHeadersInjector = new TraceHeadersInjector(); +// } +// +// } +// +// } +// +// @Override +// public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) +// throws IOException, ServletException { +// +// setTraceAndSpanId(request); +// try { +// chain.doFilter(request, response); +// } finally { +// this.diagnosticContextFacade.removeSpanId(); +// this.diagnosticContextFacade.removeTraceId(); +// } +// +// } +// +// private void setTraceAndSpanId(ServletRequest request) { +// +// String traceId = null; +// String spanId = null; +// +// if (request instanceof HttpServletRequest && this.traceIdHttpHeaderName != null +// && this.spanIdHttpHeaderName != null) { +// +// traceId = normalizeValue(((HttpServletRequest) request).getHeader(this.traceIdHttpHeaderName)); +// +// if (traceId == null) { +// LOG.debug("No trace ID found for HTTP header {}.", this.traceIdHttpHeaderName); +// } else { +// this.diagnosticContextFacade.setTraceId(traceId); +// LOG.debug("Using traceId ID {} from HTTP header {}.", traceId, this.traceIdHttpHeaderName); +// return; +// } +// +// spanId = normalizeValue(((HttpServletRequest) request).getHeader(this.spanIdHttpHeaderName)); +// +// if (spanId == null) { +// LOG.debug("No spanId ID found for HTTP header {}.", this.spanIdHttpHeaderName); +// } else { +// this.diagnosticContextFacade.setSpanId(spanId); +// LOG.debug("Using spanId ID {} from HTTP header {}.", spanId, this.spanIdHttpHeaderName); +// return; +// } +// +// } +// +// if (traceId == null && spanId == null) { +// // potential fallback if initialized before this filter... +// traceId = normalizeValue(this.diagnosticContextFacade.getTraceId()); +// spanId = normalizeValue(this.diagnosticContextFacade.getSpanId()); +// +// if (traceId != null && spanId != null) { +// LOG.debug("Trace ID and Span ID was already set to {} and {} before TraceContextFilter has been invoked.", +// traceId, spanId); +// } else { +// // no traceId ID and span ID present, inject from trace context +// TraceContext context = getActiveTraceContext(); +// this.traceHeadersInjector.inject(context, this.diagnosticContextFacade); +// LOG.debug("Injected trace ID {} and span ID {} .", context.traceIdString(), context.spanIdString()); +// } +// } +// +// } +// +// private TraceContext getActiveTraceContext() { +// +// if (this.tracer.currentSpan() == null) { +// this.tracer.nextSpan().name(LoggingConstants.SPAN_NAME).start(); +// return this.tracer.nextSpan().context(); +// } +// +// return this.tracer.currentSpan().context(); +// } +// +// @Override +// public void destroy() { +// +// } +// +// } diff --git a/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceHeadersInjector.java b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceHeadersInjector.java new file mode 100644 index 00000000..7efc59c6 --- /dev/null +++ b/modules/logging/src/main/java/com/devonfw/module/logging/common/impl/TraceHeadersInjector.java @@ -0,0 +1,23 @@ +// package com.devonfw.module.logging.common.impl; +// +// import com.devonfw.module.logging.common.api.DiagnosticContextFacade; +// +// import brave.propagation.TraceContext; +// +/// ** +// * +// */ +// public class TraceHeadersInjector implements TraceContext.Injector { +// +// @Override +// public void inject(TraceContext traceContext, DiagnosticContextFacade contextFacade) { +// +// String traceId = traceContext.traceIdString(); +// contextFacade.setTraceId(traceId); +// +// String spanId = traceContext.spanIdString(); +// contextFacade.setSpanId(spanId); +// +// } +// +// } diff --git a/modules/logging/src/test/java/com/devonfw/module/logging/common/impl/TraceContextFilterTest.java b/modules/logging/src/test/java/com/devonfw/module/logging/common/impl/TraceContextFilterTest.java new file mode 100644 index 00000000..0dde3e3e --- /dev/null +++ b/modules/logging/src/test/java/com/devonfw/module/logging/common/impl/TraceContextFilterTest.java @@ -0,0 +1,112 @@ +// package com.devonfw.module.logging.common.impl; +// +// import static org.mockito.Mockito.when; +// +// import javax.servlet.FilterConfig; +// +// import org.junit.jupiter.api.Test; +// import org.junit.jupiter.api.extension.ExtendWith; +// import org.mockito.Mock; +// import org.mockito.junit.jupiter.MockitoExtension; +// import org.springframework.test.util.ReflectionTestUtils; +// +// import com.devonfw.module.test.common.base.ModuleTest; +// +/// ** +// * +// */ +// @ExtendWith(MockitoExtension.class) +// public class TraceContextFilterTest extends ModuleTest { +// +// private static final String TRACE_ID_HEADER_NAME_PARAM = "traceIdHttpHeaderName"; +// +// private static final String TRACE_ID_HEADER_NAME_PARAM_FIELD_NAME = "TRACE_ID_HEADER_NAME_PARAM"; +// +// private static final String SPAN_ID_HEADER_NAME_PARAM = "spanIdHttpHeaderName"; +// +// private static final String SPAN_ID_HEADER_NAME_PARAM_FIELD_NAME = "SPAN_ID_HEADER_NAME_PARAM"; +// +// @Mock +// private FilterConfig config; +// +// /** +// * +// */ +// @Test +// public void testTraceAndSpanIdHttpHeaderNameAfterConstructor() { +// +// // setup +// TraceContextFilter filter = new TraceContextFilter(); +// +// // exercise +// String traceIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, TRACE_ID_HEADER_NAME_PARAM); +// String spanIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, SPAN_ID_HEADER_NAME_PARAM); +// +// // verify +// assertThat(traceIdHttpHeaderName).isNotNull(); +// assertThat(spanIdHttpHeaderName).isNotNull(); +// } +// +// /** +// * @throws Exception +// */ +// @Test +// public void testInitWithNullInitParameter() throws Exception { +// +// // setup +// TraceContextFilter filter = new TraceContextFilter(); +// String traceIdField = (String) ReflectionTestUtils.getField(TraceContextFilter.class, +// TRACE_ID_HEADER_NAME_PARAM_FIELD_NAME); +// String spanIdField = (String) ReflectionTestUtils.getField(TraceContextFilter.class, +// SPAN_ID_HEADER_NAME_PARAM_FIELD_NAME); +// +// assertThat(traceIdField).isNotNull(); +// assertThat(spanIdField).isNotNull(); +// +// when(this.config.getInitParameter(traceIdField)).thenReturn(null); +// when(this.config.getInitParameter(spanIdField)).thenReturn(null); +// +// // exercise +// filter.init(this.config); +// +// // verify +// String traceIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, TRACE_ID_HEADER_NAME_PARAM); +// assertThat(traceIdHttpHeaderName).isNotNull().isEqualTo(TraceContextFilter.TRACE_ID_HEADER_NAME_DEFAULT); +// +// String spanIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, SPAN_ID_HEADER_NAME_PARAM); +// assertThat(spanIdHttpHeaderName).isNotNull().isEqualTo(TraceContextFilter.SPAN_ID_HEADER_NAME_DEFAULT); +// } +// +// /** +// * @throws Exception +// */ +// @Test +// public void testInitWithNonDefaultParameter() throws Exception { +// +// // setup +// TraceContextFilter filter = new TraceContextFilter(); +// String traceIdField = (String) ReflectionTestUtils.getField(TraceContextFilter.class, +// TRACE_ID_HEADER_NAME_PARAM_FIELD_NAME); +// String spanIdField = (String) ReflectionTestUtils.getField(TraceContextFilter.class, +// SPAN_ID_HEADER_NAME_PARAM_FIELD_NAME); +// +// assertThat(traceIdField).isNotNull(); +// assertThat(spanIdField).isNotNull(); +// +// String nonDefaultParameter = "test"; +// +// when(this.config.getInitParameter(traceIdField)).thenReturn(nonDefaultParameter); +// when(this.config.getInitParameter(spanIdField)).thenReturn(nonDefaultParameter); +// +// // exercise +// filter.init(this.config); +// +// // verify +// String traceIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, TRACE_ID_HEADER_NAME_PARAM); +// assertThat(traceIdHttpHeaderName).isEqualTo(nonDefaultParameter); +// +// String spanIdHttpHeaderName = (String) ReflectionTestUtils.getField(filter, SPAN_ID_HEADER_NAME_PARAM); +// assertThat(spanIdHttpHeaderName).isEqualTo(nonDefaultParameter); +// } +// +// } diff --git a/modules/service/pom.xml b/modules/service/pom.xml index bba31b46..683b4b73 100644 --- a/modules/service/pom.xml +++ b/modules/service/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -47,6 +48,16 @@ javax.annotation-api test + + io.opentracing + opentracing-api + 0.33.0 + + + io.jaegertracing + jaeger-client + 0.32.0 + diff --git a/modules/service/src/main/java/com/devonfw/module/service/common/impl/header/ServiceHeaderCustomizerTraceSpanId.java b/modules/service/src/main/java/com/devonfw/module/service/common/impl/header/ServiceHeaderCustomizerTraceSpanId.java new file mode 100644 index 00000000..8d38554b --- /dev/null +++ b/modules/service/src/main/java/com/devonfw/module/service/common/impl/header/ServiceHeaderCustomizerTraceSpanId.java @@ -0,0 +1,107 @@ +package com.devonfw.module.service.common.impl.header; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.devonfw.module.logging.common.api.LoggingConstants; +import com.devonfw.module.service.common.api.header.ServiceHeaderContext; +import com.devonfw.module.service.common.api.header.ServiceHeaderCustomizer; + +import io.jaegertracing.Configuration; +import io.jaegertracing.Configuration.ReporterConfiguration; +import io.jaegertracing.Configuration.SamplerConfiguration; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.log.Fields; +import io.opentracing.tag.Tags; + +/** + * + */ +public class ServiceHeaderCustomizerTraceSpanId implements ServiceHeaderCustomizer { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceHeaderCustomizerTraceSpanId.class); + + /** + * The constructor. + */ + public ServiceHeaderCustomizerTraceSpanId() { + + super(); + } + + /** + * This method is used to create tracer with jaeger tracing. + * + * @return {@link Tracer} + */ + public Tracer initTracer() { + + SamplerConfiguration samplerConfig = SamplerConfiguration.fromEnv().withType("const").withParam(1); + ReporterConfiguration reporterConfig = ReporterConfiguration.fromEnv().withLogSpans(true); + Configuration config = new Configuration("service").withSampler(samplerConfig).withReporter(reporterConfig); + return config.getTracer(); + } + + // private final String traceIdHeaderName; + // + // private final String spanIdHeaderName; + // + // /** + // * The constructor. + // */ + // public ServiceHeaderCustomizerTraceSpanId() { + // + // this(TraceContextFilter.TRACE_ID_HEADER_NAME_DEFAULT, TraceContextFilter.SPAN_ID_HEADER_NAME_DEFAULT); + // } + // + // /** + // * The constructor. + // * + // * @param traceIdHeaderName + // * @param spanIdHeaderName + // */ + // public ServiceHeaderCustomizerTraceSpanId(String traceIdHeaderName, String spanIdHeaderName) { + // + // super(); + // this.traceIdHeaderName = traceIdHeaderName; + // this.spanIdHeaderName = spanIdHeaderName; + // } + + @Override + public void addHeaders(ServiceHeaderContext context) { + + // String traceId = MDC.get(LoggingConstants.TRACE_ID); + // String spanId = MDC.get(LoggingConstants.SPAN_ID); + // + // if (!StringUtils.isEmpty(traceId)) { + // context.setHeader(this.traceIdHeaderName, traceId); + // } + // + // if (!StringUtils.isEmpty(spanId)) { + // context.setHeader(this.spanIdHeaderName, spanId); + // } + + Tracer tracer = initTracer(); + + Span span = tracer.buildSpan(LoggingConstants.SPAN_NAME).start(); + LOG.info("new span {} has been created for the http request {}.", context.getUrl(), span.context().toSpanId()); + + try (Scope scope = tracer.scopeManager().activate(span)) { + + context.setHeader(LoggingConstants.TRACE_ID, span.context().toTraceId()); + context.setHeader(LoggingConstants.SPAN_ID, span.context().toSpanId()); + + } catch (Exception ex) { + Tags.ERROR.set(span, true); + span.log(Map.of(Fields.EVENT, "error", Fields.ERROR_OBJECT, ex, Fields.MESSAGE, ex.getMessage())); + } finally { + span.finish(); + } + + } + +}