Skip to content

Commit

Permalink
[backend] Telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
RomuDeuxfois committed Apr 24, 2024
1 parent 0a4febe commit 5f73918
Show file tree
Hide file tree
Showing 15 changed files with 592 additions and 21 deletions.
46 changes: 41 additions & 5 deletions openbas-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<springdoc.version>2.5.0</springdoc.version>
<springdoc-plugin.version>1.4</springdoc-plugin.version>
<cron-utils.version>9.2.1</cron-utils.version>
<opentelemetry.version>1.35.0</opentelemetry.version>
<opentelemetry-semconv.version>1.23.1-alpha</opentelemetry-semconv.version>
</properties>

<profiles>
Expand Down Expand Up @@ -62,6 +64,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 @@ -199,6 +213,33 @@
<artifactId>cron-utils</artifactId>
<version>${cron-utils.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</groupId>
<artifactId>opentelemetry-sdk-logs</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-logging</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

<!-- TEST -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -216,11 +257,6 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
20 changes: 17 additions & 3 deletions openbas-api/src/main/java/io/openbas/config/SessionManager.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.openbas.config;

import io.openbas.database.model.User;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
Expand All @@ -9,14 +12,12 @@
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;

@Configuration
Expand Down Expand Up @@ -104,4 +105,17 @@ public void refreshUserSessions(User databaseUser) {
public void invalidateUserSession(String userId) {
getUserSessions(userId).forEach(HttpSession::invalidate);
}

// Should have one session by user
public long getUserSessionsCount() {
int value = sessions.values()
.stream()
.map(this::extractPrincipal)
.filter((Optional::isPresent))
.map((Optional::get))
.collect(groupingBy(OpenBASPrincipal::getId))
.keySet()
.size();
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.openbas.rest.exercise.form.*;
import io.openbas.rest.helper.RestBehavior;
import io.openbas.service.*;
import io.openbas.telemetry.Tracing;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
Expand Down Expand Up @@ -424,6 +425,7 @@ public Exercise removeExerciseTeamPlayers(@PathVariable String exerciseId, @Path
// region exercises
@Transactional(rollbackOn = Exception.class)
@PostMapping("/api/exercises")
@Tracing(name = "EXERCISE CREATION", layer = "api", operation = "create")
public Exercise createExercise(@Valid @RequestBody ExerciseCreateInput input) {
Exercise exercise = new Exercise();
exercise.setUpdateAttributes(input);
Expand Down Expand Up @@ -611,7 +613,7 @@ public List<ExerciseSimple> exercises() {
: exerciseRepository.findAllGranted(currentUser().getId());
return fromIterable(exercises).stream().map(ExerciseSimple::fromExercise).toList();
}
// endregion
// endregion

// region communication
@GetMapping("/api/exercises/{exerciseId}/communications")
Expand All @@ -624,15 +626,15 @@ public Iterable<Communication> exerciseCommunications(@PathVariable String exerc
}

@GetMapping("/api/communications/attachment")
// @PreAuthorize("isExerciseObserver(#exerciseId)")
// @PreAuthorize("isExerciseObserver(#exerciseId)")
public void downloadAttachment(@RequestParam String file, HttpServletResponse response) throws IOException {
FileContainer fileContainer = fileService.getFileContainer(file).orElseThrow();
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileContainer.getName());
response.addHeader(HttpHeaders.CONTENT_TYPE, fileContainer.getContentType());
response.setStatus(HttpServletResponse.SC_OK);
fileContainer.getInputStream().transferTo(response.getOutputStream());
}
// endregion
// endregion

// region import/export
@GetMapping("/api/exercises/{exerciseId}/export")
Expand Down Expand Up @@ -763,5 +765,5 @@ public void exerciseImport(@RequestPart("file") MultipartFile file) throws Excep
importService.handleFileImport(file);
}

// endregion
// endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.openbas.scheduler.jobs.ComchecksExecutionJob;
import io.openbas.scheduler.jobs.InjectsExecutionJob;
import io.openbas.scheduler.jobs.ScenarioExecutionJob;
import io.openbas.scheduler.jobs.TelemetryExecutionJob;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -30,4 +31,10 @@ public JobDetail getScenarioExecution() {
return JobBuilder.newJob(ScenarioExecutionJob.class)
.storeDurably().withIdentity(jobKey("ScenarioExecutionJob")).build();
}

@Bean
public JobDetail getTelemetryExecutionTrigger() {
return JobBuilder.newJob(TelemetryExecutionJob.class)
.storeDurably().withIdentity(jobKey("TelemetryExecutionJob")).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.openbas.scheduler;

import lombok.RequiredArgsConstructor;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

Expand All @@ -10,19 +10,15 @@
import static org.quartz.TriggerBuilder.newTrigger;

@Component
@RequiredArgsConstructor
public class PlatformTriggers {

private PlatformJobDefinitions platformJobs;

@Autowired
public void setPlatformJobs(PlatformJobDefinitions platformJobs) {
this.platformJobs = platformJobs;
}
private final PlatformJobDefinitions platformJobs;

@Bean
public Trigger injectsExecutionTrigger() {
return newTrigger()
.forJob(platformJobs.getInjectsExecution())
.forJob(this.platformJobs.getInjectsExecution())
.withIdentity("InjectsExecutionTrigger")
.withSchedule(cronSchedule("0 0/1 * * * ?")) // Every minute align on clock
.build();
Expand All @@ -31,7 +27,7 @@ public Trigger injectsExecutionTrigger() {
@Bean
public Trigger comchecksExecutionTrigger() {
return newTrigger()
.forJob(platformJobs.getComchecksExecution())
.forJob(this.platformJobs.getComchecksExecution())
.withIdentity("ComchecksExecutionTrigger")
.withSchedule(repeatMinutelyForever())
.build();
Expand All @@ -45,4 +41,13 @@ public Trigger scenarioExecutionTrigger() {
.withSchedule(repeatMinutelyForever())
.build();
}

@Bean
public Trigger telemetryExecutionTrigger() {
return newTrigger()
.forJob(this.platformJobs.getTelemetryExecutionTrigger())
.withIdentity("TelemetryExecutionTrigger")
.withSchedule(cronSchedule("0 0/1 * * * ?")) // Every 1 hours
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.openbas.scheduler.jobs;

import io.openbas.telemetry.OpenTelemetryService;
import lombok.RequiredArgsConstructor;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

@Component
@DisallowConcurrentExecution
@RequiredArgsConstructor
public class TelemetryExecutionJob implements Job {

private final OpenTelemetryService openTelemetryService;

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
this.openTelemetryService.registerMetric();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.openbas.telemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.semconv.ResourceAttributes;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static java.util.Objects.requireNonNull;

@Service
@RequiredArgsConstructor
public class OpenTelemetryConfig {

private final Environment env;

@Bean
public OpenTelemetry openTelemetry() {
Resource resource;
try {
resource = Resource.getDefault()
.toBuilder()
.put(ResourceAttributes.SERVICE_NAME, requireNonNull(this.env.getProperty("info.app.name")))
.put(ResourceAttributes.SERVICE_VERSION, requireNonNull(this.env.getProperty("info.app.version")))
.put(ResourceAttributes.SERVICE_INSTANCE_ID, UUID.randomUUID().toString())
.putAll(Attributes.of(ResourceAttributes.HOST_IP, List.of(InetAddress.getLocalHost().getHostAddress())))
.build();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}

// -- METRICS --

// Log exporter
SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder()
.registerMetricReader(
PeriodicMetricReader.builder(LoggingMetricExporter.create()).setInterval(1, TimeUnit.MINUTES).build());

// File exporter: FIXME: use log system with retention like logstash
// sdkMeterProviderBuilder.registerMetricReader(
// PeriodicMetricReader.builder(FileMetricExporter.create(this.env.getProperty("telemetry.file"))).build()
// );

// OTLP exporter
boolean exporterOtlpEndpointEnabled = Boolean.TRUE.equals(
this.env.getProperty("telemetry.exporter.otlp.enabled", Boolean.class)
);
if (exporterOtlpEndpointEnabled) {
sdkMeterProviderBuilder.registerMetricReader(PeriodicMetricReader.builder(
OtlpHttpMetricExporter.builder().build() // Take default endpoint uri
).build());
}

SdkMeterProvider sdkMeterProvider = sdkMeterProviderBuilder
.setResource(resource)
.build();

// -- SPANS --

// Log exporter
SdkTracerProviderBuilder sdkTracerProviderBuilder = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()));

// OTLP exporter
if (exporterOtlpEndpointEnabled) {
sdkTracerProviderBuilder.addSpanProcessor(
SimpleSpanProcessor.create(OtlpGrpcSpanExporter.builder().build())
);
}

SdkTracerProvider sdkTracerProvider = sdkTracerProviderBuilder
.setResource(resource)
.build();

return OpenTelemetrySdk.builder()
.setMeterProvider(sdkMeterProvider)
.setTracerProvider(sdkTracerProvider)
.setPropagators(ContextPropagators.create(
TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance())
))
.build();
}

@Bean
public Meter meter(@NotNull final OpenTelemetry openTelemetry) {
return openTelemetry.getMeter("openbasApi-meter");
}

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

}

Loading

0 comments on commit 5f73918

Please sign in to comment.