Skip to content

Commit

Permalink
Add hl7 message tracing example with opentelemtry and micrometer
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Ohr committed Dec 18, 2024
1 parent 2b8b5bc commit 4193d0e
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 2 deletions.
5 changes: 5 additions & 0 deletions commons/ihe/hl7v2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.*
/**
* @author Christian Ohr
*/
class MessageTracerTest {
class MessageBraveTracerTest {

private static final HapiContext CONTEXT = HapiContextFactory.createHapiContext()

Expand Down Expand Up @@ -87,6 +87,7 @@ class MessageTracerTest {
assertEquals(new HashMap<>(clientSpan.tags()), new HashMap<>(serverSpan.tags()))
assertNotEquals(clientSpan.id(), serverSpan.id())
assertEquals(clientSpan.id(), serverSpan.parentId())
assertEquals(clientSpan.traceId(), serverSpan.traceId())
}

class MockReporter extends SpanHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.openehealth.ipf.commons.ihe.hl7v2.tracing


import ca.uhn.hl7v2.HapiContext
import ca.uhn.hl7v2.model.Message
import io.micrometer.tracing.SpanCustomizer
import io.micrometer.tracing.otel.bridge.ArrayListSpanProcessor
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext
import io.micrometer.tracing.otel.bridge.OtelPropagator
import io.micrometer.tracing.otel.bridge.OtelTracer
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.context.propagation.ContextPropagators
import io.opentelemetry.extension.trace.propagation.B3Propagator
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.trace.SdkTracerProvider
import io.opentelemetry.sdk.trace.samplers.Sampler
import org.junit.jupiter.api.Test
import org.openehealth.ipf.commons.ihe.hl7v2.definitions.HapiContextFactory
import org.openehealth.ipf.modules.hl7.message.MessageUtils

import static org.junit.jupiter.api.Assertions.*

/**
* @author Christian Ohr
*/
class MessageOtelTracerTest {

private static final HapiContext CONTEXT = HapiContextFactory.createHapiContext()

@Test
void traceMessage() {
ArrayListSpanProcessor reporter = new ArrayListSpanProcessor()

// Otel setup
var sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(reporter)
.setResource(Resource.getDefault())
.setSampler(Sampler.alwaysOn())
.build()
var otelPropagator = ContextPropagators.create(B3Propagator.injectingMultiHeaders())
var openTelemetrySdkBuilder = OpenTelemetrySdk.builder()
.setPropagators(otelPropagator)
.setTracerProvider(sdkTracerProvider)
var otelTracer = openTelemetrySdkBuilder.build().getTracer("io.micrometer.micrometer-tracing")

// Micrometer Otel Bridge
def propagator = new OtelPropagator(otelPropagator, otelTracer)
def tracer = new OtelTracer(otelTracer, new OtelCurrentTraceContext(), new OtelTracer.EventPublisher() {
@Override
void publishEvent(Object event) {
}
})

def messageTracer = new MessageTracer(tracer, propagator)
def sending = MessageUtils.makeMessage(CONTEXT, 'ORU', 'R01', '2.5')

messageTracer.sendMessage(sending, "producer", new Handler() {
@Override
void accept(Message receiving, SpanCustomizer sc1) {
Thread.sleep(100)
messageTracer.receiveMessage(receiving, "consumer", new Handler() {
@Override
void accept(Message received, SpanCustomizer sc2) {
assertTrue(received.get('ZTR').empty)
}
})
}
})

// Check a few things
assertEquals(2, reporter.spans().size())

def clientSpan = reporter.spans().find { span -> span.kind == SpanKind.CLIENT }
def serverSpan = reporter.spans().find { span -> span.kind == SpanKind.SERVER }

assertFalse(clientSpan.attributes.isEmpty())
assertEquals(clientSpan.attributes.asMap(), serverSpan.attributes.asMap())
assertNotEquals(clientSpan.spanId, serverSpan.spanId)
assertEquals(clientSpan.traceId, serverSpan.traceId)
assertEquals(clientSpan.spanId, serverSpan.parentSpanId)
}

}
5 changes: 5 additions & 0 deletions platform-camel/ihe/mllp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
<artifactId>brave-context-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openehealth.ipf.platform.camel.ihe.mllp.iti8;

import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.extension.trace.propagation.B3Propagator;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import org.springframework.beans.factory.FactoryBean;

public class OpenTelemetryTracerFactoryBean implements FactoryBean<Tracer> {

private final SpanProcessor spanProcessor;

public OpenTelemetryTracerFactoryBean(SpanProcessor spanProcessor) {
this.spanProcessor = spanProcessor;
}

@Override
public Tracer getObject() {
var sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(spanProcessor)
.setResource(Resource.getDefault())
.setSampler(Sampler.alwaysOn())
.build();
var openTelemetrySdkBuilder = OpenTelemetrySdk.builder()
.setPropagators(ContextPropagators.create(B3Propagator.injectingMultiHeaders()))
.setTracerProvider(sdkTracerProvider);
return openTelemetrySdkBuilder.build().getTracer("io.micrometer.micrometer-tracing");
}

@Override
public Class<?> getObjectType() {
return Tracer.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openehealth.ipf.platform.camel.ihe.mllp.iti8;

import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
import io.micrometer.tracing.otel.bridge.OtelTracer;
import org.springframework.beans.factory.FactoryBean;

public class OtelTracerFactoryBean implements FactoryBean<Tracer> {

private final io.opentelemetry.api.trace.Tracer tracer;

public OtelTracerFactoryBean(io.opentelemetry.api.trace.Tracer tracer) {
this.tracer = tracer;
}

@Override
public Tracer getObject() {
return new OtelTracer(tracer, new OtelCurrentTraceContext(), event -> {});
}

@Override
public Class<?> getObjectType() {
return Tracer.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class TestIti8 extends AbstractMllpTest {
System.setProperty(PayloadLoggerBase.PROPERTY_DISABLED, 'true')
}


@AfterAll
static void tearDownAfterClass() {
System.clearProperty(PayloadLoggerBase.PROPERTY_DISABLED)
Expand Down Expand Up @@ -83,6 +82,7 @@ class TestIti8 extends AbstractMllpTest {
assertFalse(clientSpan.tags().isEmpty())
assertEquals(new HashMap<>(clientSpan.tags()), new HashMap<>(serverSpan.tags()))
assertNotEquals(clientSpan.id(), serverSpan.id())
assertEquals(clientSpan.traceId(), serverSpan.traceId())
assertEquals(clientSpan.id(), serverSpan.parentId())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openehealth.ipf.platform.camel.ihe.mllp.iti8


import io.micrometer.tracing.otel.bridge.ArrayListSpanProcessor
import io.opentelemetry.api.trace.SpanKind
import org.junit.jupiter.api.Test
import org.openehealth.ipf.platform.camel.ihe.mllp.core.AbstractMllpTest
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration

import static org.junit.jupiter.api.Assertions.*

/**
* Unit tests for the PIX Feed transaction a.k.a. ITI-8 with OpenTelemtry trace propagation
*/
@ContextConfiguration('/iti8/iti-8-otel.xml')
class TestIti8Otel extends AbstractMllpTest {

@Autowired
private ArrayListSpanProcessor reporter

@Test
void testHappyCaseAndTrace() {
doTestHappyCaseAndAudit("pix-iti8://localhost:18083?interceptorFactories=#producerTracingInterceptor,#clientInLogger,#clientOutLogger&timeout=${TIMEOUT}", 2)
assertEquals(2, reporter.spans().size())

def clientSpan = reporter.spans().find { span -> span.kind == SpanKind.CLIENT }
def serverSpan = reporter.spans().find { span -> span.kind == SpanKind.SERVER }

assertFalse(clientSpan.attributes.isEmpty())
assertEquals(clientSpan.attributes.asMap(), serverSpan.attributes.asMap())
assertNotEquals(clientSpan.spanId, serverSpan.spanId)
assertEquals(clientSpan.traceId, serverSpan.traceId)
assertEquals(clientSpan.spanId, serverSpan.parentSpanId)
}

def doTestHappyCaseAndAudit(String endpointUri, int expectedAuditItemsCount) {
final String body = getMessageString('ADT^A01', '2.3.1')
def msg = send(endpointUri, body)
assertACK(msg)
assertAuditEvents { it.messages.size() == expectedAuditItemsCount }
}

}
67 changes: 67 additions & 0 deletions platform-camel/ihe/mllp/src/test/resources/iti8/iti-8-otel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!--
Copyright 2024 the original author or authors. Licensed under the Apache
License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
-->

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd
">

<import resource="classpath:common-mllp-beans.xml"/>

<bean id="routeBuilder"
class="org.openehealth.ipf.platform.camel.ihe.mllp.iti8.Iti8TestRouteBuilder">
</bean>

<bean id="iti8MllpExceptionHandler"
class="org.openehealth.ipf.platform.camel.ihe.mllp.iti8.Iti8MllpExceptionHandler" />

<!-- Tracing -->

<bean id="mockReporter" class="io.micrometer.tracing.otel.bridge.ArrayListSpanProcessor"/>

<bean id="otelTracer" class="org.openehealth.ipf.platform.camel.ihe.mllp.iti8.OpenTelemetryTracerFactoryBean">
<constructor-arg name="spanProcessor" ref="mockReporter"/>
</bean>

<bean id="tracer" class="org.openehealth.ipf.platform.camel.ihe.mllp.iti8.OtelTracerFactoryBean">
<constructor-arg name="tracer" ref="otelTracer"/>
</bean>

<bean id="propagator" class="io.micrometer.tracing.otel.bridge.OtelPropagator">
<constructor-arg name="propagation">
<bean class="io.opentelemetry.context.propagation.ContextPropagators" factory-method="create">
<constructor-arg>
<bean class="io.opentelemetry.extension.trace.propagation.B3Propagator" factory-method="injectingMultiHeaders"/>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg name="tracer" ref="otelTracer"/>
</bean>

<bean id="messageTracer" class="org.openehealth.ipf.commons.ihe.hl7v2.tracing.MessageTracer">
<constructor-arg ref="tracer"/>
<constructor-arg ref="propagator"/>
</bean>

<bean id="consumerTracingInterceptor" class="org.openehealth.ipf.platform.camel.ihe.hl7v2.intercept.consumer.ConsumerTracingInterceptor.Factory">
<constructor-arg ref="messageTracer"/>
</bean>

<bean id="producerTracingInterceptor" class="org.openehealth.ipf.platform.camel.ihe.hl7v2.intercept.producer.ProducerTracingInterceptor.Factory">
<constructor-arg ref="messageTracer"/>
</bean>

</beans>

0 comments on commit 4193d0e

Please sign in to comment.