Skip to content
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
2 changes: 2 additions & 0 deletions .github/workflows/publish-dev-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
id: result
with:
run-for: PR
core-branch: ${{ github.ref_name }}

docker:
name: Docker
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stress-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
stress-tests:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
jobs:
dependency-branches:
name: Dependency Branches
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
outputs:
branches: ${{ steps.result.outputs.branches }}

Expand All @@ -16,6 +16,7 @@ jobs:
id: result
with:
run-for: PR
core-branch: ${{ github.head_ref }}

test:
name: Unit tests
Expand All @@ -30,7 +31,7 @@ jobs:
# - mysql
# - mongodb

runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up JDK 21.0.7
uses: actions/setup-java@v2
Expand Down
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 All @@ -101,6 +148,7 @@ public void doCommand(String installationDir, boolean viaInstaller, String[] arg
if (!foreground) {
try {
ProcessBuilder pb = new ProcessBuilder(commands);
Logging.info("Command to be run: " + String.join(" ", pb.command()));
pb.redirectErrorStream(true);
Process process = pb.start();
try (InputStreamReader in = new InputStreamReader(process.getInputStream());
Expand Down Expand Up @@ -181,6 +229,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
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@
import io.supertokens.Main;
import io.supertokens.cronjobs.CronTask;
import io.supertokens.cronjobs.CronTaskTest;
import io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions;
import io.supertokens.multitenancy.Multitenancy;
import io.supertokens.multitenancy.MultitenancyHelper;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;

import java.util.List;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;

public class SyncCoreConfigWithDb extends CronTask {

Expand Down Expand Up @@ -62,6 +59,7 @@ public int getInitialWaitTimeSeconds() {
return 60;
}

@WithinOtelSpan
@Override
protected void doTaskForTargetTenant(TenantIdentifier targetTenant) throws Exception {
MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.*;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import io.supertokens.session.refreshToken.RefreshTokenKey;
import io.supertokens.signingkeys.AccessTokenSigningKey;
import io.supertokens.signingkeys.JWTSigningKey;
Expand Down Expand Up @@ -116,6 +117,7 @@ null, null, new JsonObject()
return StorageLayer.getMultitenancyStorage(main).getAllTenants();
}

@WithinOtelSpan
public List<TenantIdentifier> refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(
boolean reloadAllResources) {
try {
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
76 changes: 76 additions & 0 deletions src/main/java/io/supertokens/telemetry/MethodSpanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.api.trace.StatusCode;
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().getDeclaringTypeName() + "." + joinPoint.getSignature().getName())
.startSpan();
try (Scope spanScope = span.makeCurrent()) {
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(", ", "{", "}")));
try {
Object result = joinPoint.proceed(); //run the actual method
if (result != null) {
span.setAttribute("method.returns",
result.getClass().getCanonicalName() + " -> " + result);
} else {
span.setAttribute("method.returns", "void/null");
}
span.setStatus(StatusCode.OK);
return result;
} catch (Throwable e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
}
} finally {
span.end();
}
}

}
54 changes: 54 additions & 0 deletions src/main/java/io/supertokens/telemetry/TelemetryAppender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;

import java.util.Map;
import java.util.concurrent.TimeUnit;

public class TelemetryAppender {

private final static TelemetryAppender instance = new TelemetryAppender();

public static TelemetryAppender getInstance() {
return instance;
}

private TelemetryAppender() {
}

public void appendEventToCurrentSpan(String eventName, Map<String, String> eventData, long timestamp) {
Span.current().addEvent(eventName, fromMap(eventData), timestamp, TimeUnit.MILLISECONDS);
}

public void appendAttributesToCurrentSpan(Map<String, String> attributes) {
Span.current().setAllAttributes(fromMap(attributes));
}

private Attributes fromMap(Map<String, String> map) {
AttributesBuilder ab = Attributes.builder();
if(map != null) {
for (Map.Entry<String, String> e : map.entrySet()) {
ab.put(e.getKey(), e.getValue());
}
}
return ab.build();
}
}
Loading
Loading