Skip to content

Commit

Permalink
[backend] Add tracing with OpenTelemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
RomuDeuxfois authored Sep 18, 2024
1 parent 974df61 commit ba2f515
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 0 deletions.
32 changes: 32 additions & 0 deletions openbas-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<springdoc-plugin.version>1.4</springdoc-plugin.version>
<cron-utils.version>9.2.1</cron-utils.version>
<apache-poi.version>5.3.0</apache-poi.version>
<opentelemetry.version>1.42.0</opentelemetry.version>
<opentelemetry-semconv.version>1.27.0-alpha</opentelemetry-semconv.version>
</properties>

<profiles>
Expand All @@ -30,6 +32,18 @@
</profile>
</profiles>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>${opentelemetry.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.openbas</groupId>
Expand Down Expand Up @@ -177,6 +191,24 @@
<artifactId>poi-ooxml</artifactId>
<version>${apache-poi.version}</version>
</dependency>
<!-- OpenTelemetry -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.semconv</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>${opentelemetry-semconv.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.openbas.rest.inject.output.InjectOutput;
import io.openbas.service.InjectService;
import io.openbas.service.InjectTestStatusService;
import io.openbas.telemetry.Tracing;
import io.openbas.utils.pagination.SearchPaginationInput;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
Expand Down Expand Up @@ -34,6 +35,7 @@ public class ScenarioInjectApi extends RestBehavior {
@GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple")
@PreAuthorize("isScenarioObserver(#scenarioId)")
@Transactional(readOnly = true)
@Tracing(name = "Fetch injects for scenario", layer = "api", operation = "GET")
public Iterable<InjectOutput> scenarioInjectsSimple(@PathVariable @NotBlank final String scenarioId) {
return injectService.injects(fromScenario(scenarioId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.openbas.rest.scenario.response.ImportMessage;
import io.openbas.rest.scenario.response.ImportPostSummary;
import io.openbas.rest.scenario.response.ImportTestSummary;
import io.openbas.telemetry.Tracing;
import io.openbas.utils.InjectUtils;
import jakarta.annotation.Resource;
import jakarta.persistence.EntityManager;
Expand Down Expand Up @@ -144,6 +145,7 @@ public void deleteAllByIds(List<String> injectIds) {
}
}

@Tracing(name = "Fetch injects with criteria builder", layer = "service", operation = "GET")
public List<InjectOutput> injects(Specification<Inject> specification) {
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.openbas.telemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.semconv.ServerAttributes;
import io.opentelemetry.semconv.ServiceAttributes;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;

import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.Objects.requireNonNull;

@ConditionalOnProperty(prefix = "telemetry", name = "enable")
@Log
@Service
@RequiredArgsConstructor
public class OpenTelemetryConfig {

private final Environment env;

@Bean
public OpenTelemetry openTelemetry() {
Resource resource = buildResource();

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(OtlpGrpcSpanExporter.builder().build()))
.setResource(resource)
.build();

return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();
}

@Bean
public Tracer tracer(@NotNull final OpenTelemetry openTelemetry) {
return openTelemetry.getTracer("openbas-api-tracer");
}

// -- PRIVATE --

private Resource buildResource() {
ResourceBuilder resourceBuilder = Resource.getDefault().toBuilder()
.put(ServiceAttributes.SERVICE_NAME, getRequiredProperty("info.app.name"))
.put(ServiceAttributes.SERVICE_VERSION, getRequiredProperty("info.app.version"))
.put(stringKey("instance.id"), UUID.randomUUID().toString());

try {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
resourceBuilder.putAll(Attributes.of(ServerAttributes.SERVER_ADDRESS, hostAddress));
} catch (UnknownHostException e) {
log.severe("Failed to get host address: " + e.getMessage());
}

return resourceBuilder.build();
}

private String getRequiredProperty(@NotBlank final String key) {
return requireNonNull(env.getProperty(key), "Property " + key + " must not be null");
}
}
14 changes: 14 additions & 0 deletions openbas-api/src/main/java/io/openbas/telemetry/Tracing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.openbas.telemetry;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Tracing {
String name();
String layer() default "";
String operation() default "";
}
48 changes: 48 additions & 0 deletions openbas-api/src/main/java/io/openbas/telemetry/TracingAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.openbas.telemetry;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

import static io.openbas.config.SessionHelper.currentUser;

@Aspect
@Component
@RequiredArgsConstructor
@ConditionalOnBean(Tracer.class)
public class TracingAspect {

private final Tracer tracer;

@Around("@annotation(io.openbas.telemetry.Tracing)")
public Object tracing(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
Tracing tracing = method.getAnnotation(Tracing.class);

Span span = tracer.spanBuilder(tracing.name())
.setAttribute("USER", getCurrentUser())
.setAttribute("LAYER", tracing.layer())
.setAttribute("OPERATION", tracing.operation())
.setAttribute("METHOD", method.getName())
.startSpan();
try (Scope ignored = span.makeCurrent()) {
return proceedingJoinPoint.proceed();
} finally {
span.end();
}
}

private String getCurrentUser() {
return currentUser() != null ? currentUser().getId() : "anonymous";
}
}
3 changes: 3 additions & 0 deletions openbas-api/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,6 @@ lade.url=<url>
lade.session=30
lade.username=<username>
lade.password=<password>

### Telemetry
telemetry.enable=false
7 changes: 7 additions & 0 deletions openbas-dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ services:
- type: bind
source: caldera.yml
target: /usr/src/app/conf/local.yml
openbas-telemetry-jaeger:
container_name: openbas-telemetry-jaeger
image: jaegertracing/all-in-one
restart: unless-stopped
ports:
- "16686:16686"
- "4317:4317"

0 comments on commit ba2f515

Please sign in to comment.