Skip to content

Commit

Permalink
Collect telemetry (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
ricklue authored Aug 23, 2024
1 parent 664dec9 commit 1956413
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 1 deletion.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,18 @@ What version number will be published by the Jenkins server? In the frontend `po
You can also test your frontend code as Maven artifact before merging your code with the master branch. To do this, you need Maven 3.9.6 or later running on Java 17 or Java 21. Run `mvn clean install` in your checkout of the frontend. This will build the frontend artifact with exactly the version number in `pom.xml` (e.g. `0.1.0-SNAPSHOT`, no timestamp in this case) and install it on your development device. You can temporarily update the backend `pom.xml` to reference that frontend version. If you then start ladybug as described before, your work is reachable at port 80 (not 4200). You can test your work as explained in the sections on backend development.

> [!WARNING]
> When you run the Maven build on your development device, it will update `package.json`. Please do not check in that change. Otherwise, the build will not work for other developers anymore.
> When you run the Maven build on your development device, it will update `package.json`. Please do not check in that change. Otherwise, the build will not work for other developers anymore.
Collecting OpenTelemetry data
=============================

In Ladybug, there is also an API available to gather telemetry data from OpenTelemetry. When code is instrumented with the OpenTelemetry library, it is possible to use the endpoint from this API to gather it in Ladybug. For a manual OpenTelemetry-instrumentation, you can configure the Zipkin exporter and make use of the endpoint to Ladybug. See code example below:

```
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(ZipkinSpanExporter.builder().setEndpoint("http://localhost/ladybug/api/collector/").build()).build())
.setResource(resource)
.build();
```

For more info about OpenTelemetry, see https://opentelemetry.io/
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@
<version>1.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.39.0</version>
</dependency>
</dependencies>

<repositories>
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/nl/nn/testtool/Span.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package nl.nn.testtool;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import io.opentelemetry.api.trace.SpanKind;

/**
* Created a Span class to map incoming telemetry data from the endpoint. There is no library available with classes to catch such telemetry data in spans.
*/

public class Span {
private String traceId;
private String parentId;
private String id;
private SpanKind kind;
private String name;
private long timestamp;
private long duration;
private Map<String, Object> localEndpoint;
private Map<String, Object> tags;

public Span(String traceId, String parentId, String id, SpanKind kind, String name, long timestamp, long duration, Map<String, Object> localEndpoint, Map<String, Object> tags) {
this.traceId = traceId;
this.parentId = parentId;
this.id = id;
this.kind = kind;
this.name = name;
this.timestamp = timestamp;
this.duration = duration;
this.localEndpoint = localEndpoint;
this.tags = tags;
}

public Span() {
}

public String getTraceId() {
return traceId;
}

public String getParentId() {
return parentId;
}

public String getId() {
return id;
}

public String getName() {
return name;
}

public long getTimestamp() {
return timestamp;
}

public long getDuration() {
return duration;
}

public Map<String, Object> getLocalEndpoint() {
return localEndpoint;
}

public Map<String, Object> getTags() {
return tags;
}

public String getKind() {
if (kind == null) {
return null;
}
return kind.toString();
}

public Map<String, Object> toHashmap() {
String date = LocalDateTime.ofInstant(Instant.ofEpochMilli(this.timestamp / 1000), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("\"traceId\"", "\"" + this.traceId + "\"");
map.put("\"parentId\"", "\"" + this.parentId + "\"");
map.put("\"id\"", "\"" + this.id + "\"");
map.put("\"kind\"", "\"" + this.kind + "\"");
map.put("\"name\"", "\"" + this.name + "\"");
map.put("\"time\"", "\"" + date + "\"");
map.put("\"duration\"", "\"" + this.duration + "\"");
map.put("\"localEndpoint\"", "\"" + this.localEndpoint + "\"");
map.put("\"tags\"", "\"" + this.tags + "\"");

return map;
}
}
6 changes: 6 additions & 0 deletions src/main/java/nl/nn/testtool/web/ApiAuthorizationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public ApiAuthorizationFilter() {
setObserverRoles(null);
setDataAdminRoles(null);
setTesterRoles(null);
setWebServiceRoles(null);
constructorDone = true;
}

Expand Down Expand Up @@ -115,6 +116,11 @@ public void setTesterRoles(List<String> testerRoles) {
addConfigurationPart("POST/" + ApiServlet.LADYBUG_API_PATH + "/runner/run/.*", testerRoles);
}

public void setWebServiceRoles(List<String> webServiceRoles) {
if (constructorDone) log.info("Set web service roles");
addConfigurationPart("POST/" + ApiServlet.LADYBUG_API_PATH + "/collector/.*$", webServiceRoles);
}

public void setLadybugApiRoles(Map<String, List<String>> ladybugApiRoles) {
log.info("Set Ladybug api roles");
for (String path : ladybugApiRoles.keySet()) {
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/nl/nn/testtool/web/api/CollectorApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright 2021-2024 WeAreFrank!
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 nl.nn.testtool.web.api;

import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.Setter;
import nl.nn.testtool.Span;
import nl.nn.testtool.TestTool;
import nl.nn.testtool.web.ApiServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;

@Path("/" + ApiServlet.LADYBUG_API_PATH + "/collector")
public class CollectorApi extends ApiBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private @Setter @Inject @Autowired TestTool testTool;

@POST
@Path("/")
public Response collectSpans(Span[] trace) {
processSpans(trace);

return Response.ok().build();
}

@POST
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
public Response collectSpansJson(Span[] trace) {
processSpans(trace);

return Response.ok().build();
}

private void processSpans(Span[] trace) {
ArrayList<String> parentIds = new ArrayList<>();
for (Span span: trace) {
if (span.getParentId() != null && !parentIds.contains(span.getParentId())) {
parentIds.add(span.getParentId());
}
}
ArrayList<String> endpoints = new ArrayList<>();
for (int i = trace.length - 1; i >= 0; i--) {
if (trace[i].getParentId() == null) {
testTool.startpoint(trace[i].getTraceId(), null, trace[i].getName(), trace[i].toHashmap().toString());
endpoints.add(trace[i].getName());
} else {
if (parentIds.contains(trace[i].getId())) {
testTool.startpoint(trace[i].getTraceId(), null, trace[i].getName(), trace[i].toHashmap().toString());
endpoints.add(trace[i].getName());
} else {
testTool.infopoint(trace[i].getTraceId(), null, trace[i].getName(), trace[i].toHashmap().toString());
}
}
}
for (int i = endpoints.size() - 1; i >= 0; i--) {
testTool.endpoint(trace[0].getTraceId(), null, endpoints.get(i), "Endpoint");
}
}
}
4 changes: 4 additions & 0 deletions src/main/resources/ladybug/cxf-beans.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
<bean id="ladybug-api-bus" class="org.apache.cxf.bus.spring.SpringBus" destroy-method="shutdown">
<property name="id" value="ladybug-api-bus"/>
</bean>
<bean id="gZipInterceptor" class="org.apache.cxf.transport.common.gzip.GZIPInInterceptor" />

<jaxrs:server id="Ladybug-Api" bus="ladybug-api-bus" address="/" basePackages="nl.nn.testtool.web.api">
<jaxrs:inInterceptors>
<ref bean="gZipInterceptor" />
</jaxrs:inInterceptors>
<jaxrs:providers>
<bean id="jsonProvider" class="com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider"/>
<bean id="apiAuthorizationFilter" class="nl.nn.testtool.web.ApiAuthorizationFilter" autowire="byName"/>
Expand Down

0 comments on commit 1956413

Please sign in to comment.