Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8fd4935
feat: add java cli options for javaagent and jmx
tamassoltesz Sep 17, 2025
61477b5
fix: gitignore changes to allow resources jars
tamassoltesz Sep 22, 2025
583fd76
feat: add otel javaagent jars
tamassoltesz Sep 22, 2025
68f2de4
fix: loading agent from the agent dir
tamassoltesz Sep 22, 2025
d91318d
fix: changelog and build version
tamassoltesz Sep 22, 2025
29ec494
fix: proper agent attach command on unix-like systems
tamassoltesz Sep 22, 2025
c206103
feat: aspect for wrapping methods in otel span
tamassoltesz Sep 25, 2025
e4be070
fix: add exception records to spans
tamassoltesz Sep 26, 2025
f050dab
fix: use javaagent's otel if present
tamassoltesz Sep 26, 2025
a5609d0
fix: fixing agent loading at startup
tamassoltesz Sep 26, 2025
315ea7b
fix: add logging for the command that's being run
tamassoltesz Sep 30, 2025
0bf21f7
fix: pinpoint runner image version
tamassoltesz Oct 10, 2025
6a36676
fix: pinpoint runner image version
tamassoltesz Oct 10, 2025
55e5df0
fix: tell the core branch name for the workflow
tamassoltesz Oct 10, 2025
38c2b80
fix: build docker image with the right branches
tamassoltesz Oct 10, 2025
73fd8b6
fix: publish-dev-docker formatting
tamassoltesz Oct 10, 2025
31a546f
fix: using an other variable for deciding the current branch name
tamassoltesz Oct 10, 2025
2eae4fb
fix: using an other variable for deciding the current branch name
tamassoltesz Oct 10, 2025
966ef53
fix: update cli dependencies to make it the same version as core
tamassoltesz Oct 13, 2025
9d6821c
fix: update implementationDependencies.json with aspectjrt
tamassoltesz Oct 13, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ gradle-app.setting
!cli/jar/**/*.jar
!downloader/jar/**/*.jar
!ee/jar/**/*.jar
!src/main/resources/**/*.jar

*target*
*.war
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [11.1.1]

- Adds opentelemetry-javaagent to the core distribution

## [11.1.0]

- Adds hikari logs to opentelemetry
Expand Down
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
plugins {
id 'application'
id 'java-library'
id "io.freefair.aspectj" version "8.13" //same as gradle version!
}
compileJava { options.encoding = "UTF-8" }
compileTestJava { options.encoding = "UTF-8" }
Expand All @@ -26,7 +27,7 @@ java {
}
}

version = "11.1.0"
version = "11.1.1"

repositories {
mavenCentral()
Expand All @@ -47,7 +48,6 @@ dependencies {
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.2'


// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
api group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.0.8'

Expand Down Expand Up @@ -99,6 +99,7 @@ dependencies {

implementation("io.opentelemetry.semconv:opentelemetry-semconv")

implementation('org.aspectj:aspectjrt:1.9.24')

compileOnly project(":supertokens-plugin-interface")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,38 @@ public void doCommand(String installationDir, boolean viaInstaller, String[] arg
String host = CLIOptionsParser.parseOption("--host", args);
boolean foreground = CLIOptionsParser.hasKey("--foreground", args);
boolean forceNoInMemDB = CLIOptionsParser.hasKey("--no-in-mem-db", args);
boolean javaagentEnabled = CLIOptionsParser.hasKey("--javaagent", args);
boolean jmxEnabled = CLIOptionsParser.hasKey("--jmx", args);
String jmxPort = CLIOptionsParser.parseOption("--jmx-port", args);
String jmxAuthenticate = CLIOptionsParser.parseOption("--jmx-authenticate", args);
String jmxSSL = CLIOptionsParser.parseOption("--jmx-ssl", args);

List<String> commands = new ArrayList<>();
if (OperatingSystem.getOS() == OperatingSystem.OS.WINDOWS) {
commands.add(installationDir + "jre\\bin\\java.exe");
commands.add("-classpath");
commands.add("\"" + installationDir + "core\\*\";\"" + installationDir + "plugin-interface\\*\"");
if (javaagentEnabled) {
commands.add("-javaagent:\"" + installationDir + "\\agent\\opentelemetry-javaagent.jar\"");
}
if (jmxEnabled) {
commands.add("-Dcom.sun.management.jmxremote");
if (jmxPort != null) {
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
} else {
commands.add("-Dcom.sun.management.jmxremote.port=9010");
}
if (jmxAuthenticate != null) {
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
} else {
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
}
if (jmxSSL != null) {
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
} else {
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
}
}
if (space != null) {
commands.add("-Xmx" + space + "M");
}
Expand Down Expand Up @@ -77,6 +103,27 @@ public void doCommand(String installationDir, boolean viaInstaller, String[] arg
commands.add("-classpath");
commands.add(
installationDir + "core/*:" + installationDir + "plugin-interface/*:" + installationDir + "ee/*");
if (javaagentEnabled) {
commands.add("-javaagent:" + installationDir + "/agent/opentelemetry-javaagent.jar");
}
if (jmxEnabled) {
commands.add("-Dcom.sun.management.jmxremote");
if (jmxPort != null) {
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
} else {
commands.add("-Dcom.sun.management.jmxremote.port=9010");
}
if (jmxAuthenticate != null) {
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
} else {
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
}
if (jmxSSL != null) {
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
} else {
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
}
}
if (space != null) {
commands.add("-Xmx" + space + "M");
}
Expand Down Expand Up @@ -181,6 +228,13 @@ protected List<Option> getOptionsAndDescription() {
new Option("--foreground", "Runs this instance of SuperTokens in the foreground (not as a daemon)"));
options.add(
new Option("--with-temp-dir", "Uses the passed dir as temp dir, instead of the internal default."));
options.add(new Option("--javaagent", "Enables the OpenTelemetry Javaagent for tracing and metrics."));
options.add(new Option("--jmx", "Enables JMX management and monitoring."));
options.add(new Option("--jmx-port", "Sets the port for JMX. Defaults to 9010 if --jmx is passed."));
options.add(new Option("--jmx-authenticate",
"Sets whether JMX authentication is enabled or not. Defaults to false if --jmx is passed."));
options.add(new Option("--jmx-ssl",
"Sets whether JMX SSL is enabled or not. Defaults to false if --jmx is passed."));
return options;
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/supertokens/ResourceDistributor.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import org.jetbrains.annotations.TestOnly;

import javax.annotation.Nonnull;
Expand All @@ -31,6 +32,7 @@
// the purpose of this class is to tie singleton classes to s specific main instance. So that
// when the main instance dies, those singleton classes die too.

@WithinOtelSpan
public class ResourceDistributor {
private final Map<KeyClass, SingletonResource> resources = new HashMap<>(1);
private final Main main;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/supertokens/storageLayer/StorageLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
import io.supertokens.telemetry.TelemetryProvider;
import io.supertokens.useridmapping.UserIdType;
Expand All @@ -49,6 +50,7 @@
import java.net.URLClassLoader;
import java.util.*;

@WithinOtelSpan
public class StorageLayer extends ResourceDistributor.SingletonResource {

public static final String RESOURCE_KEY = "io.supertokens.storageLayer.StorageLayer";
Expand Down Expand Up @@ -386,6 +388,7 @@ public static Storage getBaseStorage(Main main) {
}
}

@WithinOtelSpan
public static Storage getStorage(TenantIdentifier tenantIdentifier, Main main)
throws TenantOrAppNotFoundException {
return getInstance(tenantIdentifier, main).storage;
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/io/supertokens/telemetry/MethodSpanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* 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 io.supertokens.telemetry;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Aspect
public class MethodSpanner {

@Around("execution(* (@io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan *).*(..))")
public Object anyMethodInClassAnnotatedWithWithinOtelSpan(ProceedingJoinPoint joinPoint) throws Throwable {
return withinOtelSpan(joinPoint);
}

@Around("execution(@io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan * *(..))")
public Object withinOtelSpan(ProceedingJoinPoint joinPoint) throws Throwable {
Span span = GlobalOpenTelemetry.get().getTracer("core-tracer").spanBuilder(joinPoint.getSignature().getName()).startSpan();
try (Scope spanScope = span.makeCurrent()) {
Map<String, String> methodArguments = new HashMap<>();
for (Object argument : joinPoint.getArgs()) {
methodArguments.put(argument.getClass().getCanonicalName(), String.valueOf(argument));
}
span.setAttribute("method.arguments", methodArguments.keySet().stream().map(key -> key + ": " + methodArguments.get(key))
.collect(Collectors.joining(", ", "{", "}")));
Comment on lines 45 to 55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code as written could throw a NullPointerException if any argument in joinPoint.getArgs() is null. Consider adding null checks before accessing argument.getClass():

Map<String, String> methodArguments = new HashMap<>();
for (Object argument : joinPoint.getArgs()) {
    if (argument != null) {
        methodArguments.put(argument.getClass().getCanonicalName(), String.valueOf(argument));
    } else {
        methodArguments.put("null", "null");
    }
}

This ensures the code handles null arguments gracefully while still capturing all method parameters for telemetry.

Suggested change
Map<String, String> methodArguments = new HashMap<>();
for (Object argument : joinPoint.getArgs()) {
methodArguments.put(argument.getClass().getCanonicalName(), String.valueOf(argument));
}
span.setAttribute("method.arguments", methodArguments.keySet().stream().map(key -> key + ": " + methodArguments.get(key))
.collect(Collectors.joining(", ", "{", "}")));
Map<String, String> methodArguments = new HashMap<>();
for (Object argument : joinPoint.getArgs()) {
if (argument != null) {
methodArguments.put(argument.getClass().getCanonicalName(), String.valueOf(argument));
} else {
methodArguments.put("null", "null");
}
}
span.setAttribute("method.arguments", methodArguments.keySet().stream().map(key -> key + ": " + methodArguments.get(key))
.collect(Collectors.joining(", ", "{", "}")));

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


Object result = joinPoint.proceed(); //run the actual method

if (result != null) {
span.setAttribute("method.returnType", result.getClass().getCanonicalName());
span.setAttribute("method.returnValue", String.valueOf(result));
} else {
span.setAttribute("method.returnType", "void");
}
return result;
} finally {
span.end();
}
}

}
2 changes: 2 additions & 0 deletions src/main/java/io/supertokens/telemetry/TelemetryProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
public class TelemetryProvider extends ResourceDistributor.SingletonResource implements OtelProvider {

private final OpenTelemetry openTelemetry;
private final Main main;

public static synchronized TelemetryProvider getInstance(Main main) {
TelemetryProvider instance = null;
Expand Down Expand Up @@ -201,5 +202,6 @@ public static void closeTelemetry(Main main) {

private TelemetryProvider(Main main) {
openTelemetry = initializeOpenTelemetry(main);
this.main = main;
}
}
3 changes: 3 additions & 0 deletions src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.utils.SemVer;
Expand All @@ -51,6 +52,7 @@
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

@WithinOtelSpan
public abstract class WebserverAPI extends HttpServlet {

private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -358,6 +360,7 @@ private AppIdentifier getAppIdentifierWithoutVerifying(HttpServletRequest req) t
return new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req));
}

@WithinOtelSpan
protected AppIdentifier getAppIdentifier(HttpServletRequest req)
throws ServletException, TenantOrAppNotFoundException {
AppIdentifier appIdentifier = getAppIdentifierWithoutVerifying(req);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/supertokens/webserver/api/core/HelloAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.utils.RateLimiter;
import io.supertokens.webserver.WebserverAPI;
Expand All @@ -32,6 +33,7 @@

// the point of this API is only to test that the server is up and running.

@WithinOtelSpan
public class HelloAPI extends WebserverAPI {

private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -75,6 +77,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO
handleRequest(req, resp);
}


private void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
// API is app specific

Expand Down
Binary file added src/main/resources/opentelemetry-javaagent.jar
Binary file not shown.
Binary file not shown.
Loading