Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[telemetry] Add telemetry with first metric #636

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading