From 05c4cca51faf20314c559adb1b5b283f6cb29b98 Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Tue, 19 Aug 2025 12:16:59 +0200 Subject: [PATCH 1/4] feat: add hikari logs to otel --- CHANGELOG.md | 4 ++ build.gradle | 2 +- .../java/io/supertokens/inmemorydb/Start.java | 3 +- .../java/io/supertokens/output/Logging.java | 14 ++--- .../storageLayer/StorageLayer.java | 4 +- .../telemetry/TelemetryProvider.java | 58 +++++++++++++++---- 6 files changed, 63 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8dd46005..77d5d57f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [11.0.6] + +- Adds hikari logs to opentelemetry + ## [11.0.5] - Adds all logs to telemetry which were logged with `io/supertokens/output/Logging.java` diff --git a/build.gradle b/build.gradle index b37778ccd..cfcba89ad 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ java { } } -version = "11.0.5" +version = "11.0.6" repositories { mavenCentral() diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 0cdad6c95..46f355cb8 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -65,6 +65,7 @@ import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException; import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException; +import io.supertokens.pluginInterface.opentelemetry.OtelProvider; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser; @@ -202,7 +203,7 @@ public void assertThatConfigFromSameUserPoolIsNotConflicting(JsonObject otherCon } @Override - public void initFileLogging(String infoLogPath, String errorLogPath) { + public void initFileLogging(String infoLogPath, String errorLogPath, OtelProvider otelProvider) { // no op } diff --git a/src/main/java/io/supertokens/output/Logging.java b/src/main/java/io/supertokens/output/Logging.java index 8806b6569..4e0335b35 100644 --- a/src/main/java/io/supertokens/output/Logging.java +++ b/src/main/java/io/supertokens/output/Logging.java @@ -66,7 +66,7 @@ private Logging(Main main) { Storage storage = StorageLayer.getBaseStorage(main); if (storage != null) { storage.initFileLogging(Config.getBaseConfig(main).getInfoLogPath(main), - Config.getBaseConfig(main).getErrorLogPath(main)); + Config.getBaseConfig(main).getErrorLogPath(main), TelemetryProvider.getInstance(main)); } try { // we wait here for a bit so that the loggers can be properly initialised.. @@ -114,7 +114,7 @@ public static void debug(Main main, TenantIdentifier tenantIdentifier, String ms if (getInstance(main) != null) { String formattedMsg = getFormattedMessage(tenantIdentifier, msg); getInstance(main).infoLogger.debug(formattedMsg); - TelemetryProvider.createLogEvent(main, tenantIdentifier, formattedMsg, "debug"); + TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, formattedMsg, "debug"); } } catch (NullPointerException e) { // sometimes logger.debug throws a null pointer exception... @@ -166,7 +166,7 @@ public static void info(Main main, TenantIdentifier tenantIdentifier, String msg getInstance(main).infoLogger.info(msg); } - TelemetryProvider.createLogEvent(main, tenantIdentifier, msg, "info"); + TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, msg, "info"); } catch (NullPointerException ignored) { } } @@ -180,7 +180,7 @@ public static void warn(Main main, TenantIdentifier tenantIdentifier, String msg msg = getFormattedMessage(tenantIdentifier, msg); if (getInstance(main) != null) { getInstance(main).errorLogger.warn(msg); - TelemetryProvider.createLogEvent(main, tenantIdentifier, msg, "warn"); + TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, msg, "warn"); } } catch (NullPointerException ignored) { } @@ -202,7 +202,7 @@ public static void error(Main main, TenantIdentifier tenantIdentifier, String er if (getInstance(main) != null) { String formattedMessage = getFormattedMessage(tenantIdentifier, err); getInstance(main).errorLogger.error(formattedMessage); - TelemetryProvider.createLogEvent(main, tenantIdentifier, formattedMessage, "error"); + TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, formattedMessage, "error"); } if (toConsoleAsWell || getInstance(main) == null) { systemErr(prependTenantIdentifierToMessage(tenantIdentifier, err)); @@ -236,8 +236,8 @@ public static void error(Main main, TenantIdentifier tenantIdentifier, String me message = message.trim(); if (getInstance(main) != null) { getInstance(main).errorLogger.error(getFormattedMessage(tenantIdentifier, message, e)); - TelemetryProvider - .createLogEvent(main, tenantIdentifier, getFormattedMessage(tenantIdentifier, message, e), + TelemetryProvider.getInstance(main) + .createLogEvent(tenantIdentifier, getFormattedMessage(tenantIdentifier, message, e), "error"); } if (toConsoleAsWell || getInstance(main) == null) { diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index c054b7feb..55f3fd80f 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -37,6 +37,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; +import io.supertokens.telemetry.TelemetryProvider; import io.supertokens.useridmapping.UserIdType; import jakarta.servlet.ServletException; import org.jetbrains.annotations.TestOnly; @@ -321,7 +322,8 @@ public static void loadAllTenantStorage(Main main, TenantConfig[] tenants) new ArrayList<>(storageToTenantIdentifiersMap.get(((StorageLayer) resource).storage))); ((StorageLayer) resource).storage.initFileLogging( Config.getBaseConfig(main).getInfoLogPath(main), - Config.getBaseConfig(main).getErrorLogPath(main)); + Config.getBaseConfig(main).getErrorLogPath(main), + TelemetryProvider.getInstance(main)); } catch (DbInitException e) { Logging.error(main, TenantIdentifier.BASE_TENANT, e.getMessage(), false, e); diff --git a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java index 5a789d7c4..7ec045ad7 100644 --- a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java +++ b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java @@ -20,6 +20,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; @@ -36,19 +37,19 @@ import io.supertokens.config.Config; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.opentelemetry.OtelProvider; import org.jetbrains.annotations.TestOnly; +import java.util.Map; import java.util.concurrent.TimeUnit; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; -public class TelemetryProvider extends ResourceDistributor.SingletonResource { - - private static final String RESOURCE_ID = "io.supertokens.telemetry.TelemetryProvider"; +public class TelemetryProvider extends ResourceDistributor.SingletonResource implements OtelProvider { private final OpenTelemetry openTelemetry; - private static synchronized TelemetryProvider getInstance(Main main) { + public static synchronized TelemetryProvider getInstance(Main main) { TelemetryProvider instance = null; try { instance = (TelemetryProvider) main.getResourceDistributor() @@ -63,15 +64,22 @@ public static void initialize(Main main) { .setResource(TenantIdentifier.BASE_TENANT, RESOURCE_ID, new TelemetryProvider(main)); } - public static void createLogEvent(Main main, TenantIdentifier tenantIdentifier, String logMessage, + @Override + public void createLogEvent(TenantIdentifier tenantIdentifier, String logMessage, String logLevel) { - getInstance(main).openTelemetry.getTracer("core-tracer") - .spanBuilder(logLevel) - .setParent(Context.current()) - .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) - .setAttribute("tenant.appId", tenantIdentifier.getAppId()) - .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()) - .startSpan() + createLogEvent(tenantIdentifier, logMessage, logLevel, Map.of()); + } + + @Override + public void createLogEvent(TenantIdentifier tenantIdentifier, String logMessage, + String logLevel, Map additionalAttributes) { + if (openTelemetry == null) { + throw new IllegalStateException("OpenTelemetry is not initialized. Call initialize() first."); + } + SpanBuilder spanBuilder = createSpanBuilder(tenantIdentifier, logLevel, additionalAttributes); + + + spanBuilder.startSpan() .addEvent("log", Attributes.builder() .put("message", logMessage) @@ -80,6 +88,32 @@ public static void createLogEvent(Main main, TenantIdentifier tenantIdentifier, .end(); } + private SpanBuilder createSpanBuilder(TenantIdentifier tenantIdentifier, String spanName, + Map additionalAttributes) { + SpanBuilder spanBuilder = openTelemetry.getTracer("core-tracer") + .spanBuilder(spanName) + .setParent(Context.current()); + + return addAttributesToSpanBuilder(spanBuilder, tenantIdentifier, additionalAttributes); + } + + private SpanBuilder addAttributesToSpanBuilder(SpanBuilder spanBuilder, TenantIdentifier tenantIdentifier, + Map additionalAttributes) { + spanBuilder + .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) + .setAttribute("tenant.appId", tenantIdentifier.getAppId()) + .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()); + + if (additionalAttributes != null && !additionalAttributes.isEmpty()) { + // Add additional attributes to the span + for (Map.Entry attribute : additionalAttributes.entrySet()) { + spanBuilder.setAttribute(attribute.getKey(), attribute.getValue()); + } + } + + return spanBuilder; + } + public static Span startSpan(Main main, TenantIdentifier tenantIdentifier, String spanName) { Span span = getInstance(main).openTelemetry.getTracer("core-tracer") .spanBuilder(spanName) From daabf73c18654e838c840ed41a845c5e08d18492 Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Tue, 26 Aug 2025 09:44:40 +0200 Subject: [PATCH 2/4] chore: update build version and changelog --- CHANGELOG.md | 2 +- build.gradle | 2 +- pluginInterfaceSupported.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d5d57f1..c3df69338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -## [11.0.6] +## [11.1.0 - Adds hikari logs to opentelemetry diff --git a/build.gradle b/build.gradle index cfcba89ad..4502c5993 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ java { } } -version = "11.0.6" +version = "11.1.0" repositories { mavenCentral() diff --git a/pluginInterfaceSupported.json b/pluginInterfaceSupported.json index 06c8cdfaf..144107a2b 100644 --- a/pluginInterfaceSupported.json +++ b/pluginInterfaceSupported.json @@ -1,6 +1,6 @@ { "_comment": "contains a list of plugin interfaces branch names that this core supports", "versions": [ - "8.0" + "8.1" ] } \ No newline at end of file From 239e21c52f021bf79bc4331d38f9b6880ca57c99 Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Wed, 27 Aug 2025 16:56:34 +0200 Subject: [PATCH 3/4] feat: otel monitor all incoming requests --- .../telemetry/TelemetryProvider.java | 76 +++++++++++-------- .../telemetry/WebRequestTelemetryHandler.java | 68 +++++++++++++++++ .../io/supertokens/webserver/PathRouter.java | 27 ++++++- .../supertokens/webserver/WebserverAPI.java | 6 +- .../webserver/api/core/HelloAPI.java | 23 +++++- .../api/dashboard/DashboardSignInAPI.java | 15 ++++ 6 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java diff --git a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java index 7ec045ad7..e035d2edc 100644 --- a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java +++ b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; @@ -38,6 +39,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.opentelemetry.OtelProvider; +import io.supertokens.pluginInterface.opentelemetry.RunnableWithOtel; import org.jetbrains.annotations.TestOnly; import java.util.Map; @@ -88,6 +90,37 @@ public void createLogEvent(TenantIdentifier tenantIdentifier, String logMessage, .end(); } + @Override + public T wrapInSpanWithReturn(TenantIdentifier tenantIdentifier, String spanName, + Map additionalAttributes, RunnableWithOtel runnableWithOtel) { + if (openTelemetry == null) { + throw new IllegalStateException("OpenTelemetry is not initialized. Call initialize() first."); + } + + SpanBuilder spanBuilder = createSpanBuilder(tenantIdentifier, spanName, additionalAttributes); + + Span span = spanBuilder.startSpan(); + try (Scope scope = span.makeCurrent()) { + return (T) runnableWithOtel.runWithReturnValue(); + } finally { + span.end(); + } + } + + @Override + public void createSpanWithAttributes(TenantIdentifier tenantIdentifier, String spanName, + Map additionalAttributes) { + Span span = createSpanBuilder(tenantIdentifier, spanName, additionalAttributes) + .setParent(Context.current()) + .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) + .setAttribute("tenant.appId", tenantIdentifier.getAppId()) + .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()) + .startSpan(); + + span.end(); + } + + private SpanBuilder createSpanBuilder(TenantIdentifier tenantIdentifier, String spanName, Map additionalAttributes) { SpanBuilder spanBuilder = openTelemetry.getTracer("core-tracer") @@ -99,10 +132,17 @@ private SpanBuilder createSpanBuilder(TenantIdentifier tenantIdentifier, String private SpanBuilder addAttributesToSpanBuilder(SpanBuilder spanBuilder, TenantIdentifier tenantIdentifier, Map additionalAttributes) { - spanBuilder - .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) - .setAttribute("tenant.appId", tenantIdentifier.getAppId()) - .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()); + if (tenantIdentifier == null) { + spanBuilder + .setAttribute("tenant.connectionUriDomain", "unknown") + .setAttribute("tenant.appId", "unknown") + .setAttribute("tenant.tenantId", "unknown"); + } else { + spanBuilder + .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) + .setAttribute("tenant.appId", tenantIdentifier.getAppId()) + .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()); + } if (additionalAttributes != null && !additionalAttributes.isEmpty()) { // Add additional attributes to the span @@ -114,34 +154,6 @@ private SpanBuilder addAttributesToSpanBuilder(SpanBuilder spanBuilder, TenantId return spanBuilder; } - public static Span startSpan(Main main, TenantIdentifier tenantIdentifier, String spanName) { - Span span = getInstance(main).openTelemetry.getTracer("core-tracer") - .spanBuilder(spanName) - .setParent(Context.current()) - .setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain()) - .setAttribute("tenant.appId", tenantIdentifier.getAppId()) - .setAttribute("tenant.tenantId", tenantIdentifier.getTenantId()) - .startSpan(); - - span.makeCurrent(); // Set the span as the current context - return span; - } - - public static Span endSpan(Span span) { - if (span != null) { - span.end(); - } - return span; - } - - public static Span addEventToSpan(Span span, String eventName, Attributes attributes) { - if (span != null) { - span.addEvent(eventName, attributes, System.currentTimeMillis(), TimeUnit.MILLISECONDS); - } - return span; - } - - private static OpenTelemetry initializeOpenTelemetry(Main main) { if (getInstance(main) != null && getInstance(main).openTelemetry != null) { return getInstance(main).openTelemetry; // already initialized diff --git a/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java b/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java new file mode 100644 index 000000000..1804f7cf2 --- /dev/null +++ b/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java @@ -0,0 +1,68 @@ +/* + * 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.supertokens.Main; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.opentelemetry.OtelProvider; +import io.supertokens.pluginInterface.opentelemetry.RunnableWithOtel; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class WebRequestTelemetryHandler { + + private final OtelProvider telemetryProvider; + + public WebRequestTelemetryHandler(Main main) { + this.telemetryProvider = TelemetryProvider.getInstance(main); + } + + public void wrapRequestInSpan(HttpServletRequest servletRequest, TenantIdentifier tenantIdentifier, RunnableWithOtel requestHandler) { + Map requestAttributes = getRequestAttributes(servletRequest); + telemetryProvider.wrapInSpanWithReturn(tenantIdentifier, "httpRequest", requestAttributes, requestHandler); + } + + public void createSpan(TenantIdentifier tenantIdentifier, String name, Map attributes) { + telemetryProvider.createSpanWithAttributes(tenantIdentifier, name, attributes); + } + + public T wrapInSpan(TenantIdentifier tenantIdentifier, String name, Map attributes, RunnableWithOtel runnable) { + return (T) telemetryProvider.wrapInSpanWithReturn(tenantIdentifier, name, attributes, runnable); + } + + + private Map getRequestAttributes(HttpServletRequest servletRequest) { + Map requestAttributes = new HashMap<>(); + requestAttributes.put("http.method", servletRequest.getMethod()); + requestAttributes.put("http.url", servletRequest.getRequestURL().toString()); + if (servletRequest.getQueryString() != null) { + requestAttributes.put("http.query_string", servletRequest.getQueryString()); + } + requestAttributes.put("http.user_agent", servletRequest.getHeader("User-Agent")); + requestAttributes.put("http.client_ip", servletRequest.getRemoteAddr()); + Enumeration headersEnumeration = servletRequest.getHeaderNames(); + while(headersEnumeration.hasMoreElements()) { + String headerName = headersEnumeration.nextElement(); + requestAttributes.put("http.header." + headerName.toLowerCase(), servletRequest.getHeader(headerName)); + } + return requestAttributes; + } + +} diff --git a/src/main/java/io/supertokens/webserver/PathRouter.java b/src/main/java/io/supertokens/webserver/PathRouter.java index 7bbc34099..b8e7129bf 100644 --- a/src/main/java/io/supertokens/webserver/PathRouter.java +++ b/src/main/java/io/supertokens/webserver/PathRouter.java @@ -17,6 +17,8 @@ package io.supertokens.webserver; import io.supertokens.Main; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -111,6 +113,29 @@ private WebserverAPI getAPIThatMatchesPath(HttpServletRequest req) { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { - getAPIThatMatchesPath(req).service(req, resp); + TenantIdentifier tenantIdentifier = null; + try { + tenantIdentifier = getTenantIdentifierWithoutVerifying(req); + } catch (ServletException e) { + //servlet exception can be thrown by getTenantIdentifierWithoutVerifying(req) + // we are going to ignore it + } + + try { + otelTelemetryWebHandler.wrapRequestInSpan(req, tenantIdentifier, () -> { + try { + getAPIThatMatchesPath(req).service(req, resp); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + }); + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof IOException) { + throw (IOException) runtimeException.getCause(); //unwrap the IOException so that it can be handled + // properly by the caller + } + throw runtimeException; + } } } diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index 7be51494c..7554a13be 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -33,6 +33,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.telemetry.WebRequestTelemetryHandler; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; import jakarta.servlet.FilterChain; @@ -58,6 +59,8 @@ public abstract class WebserverAPI extends HttpServlet { public static final Set supportedVersions = new HashSet<>(); private String rid; + protected WebRequestTelemetryHandler otelTelemetryWebHandler; + static { supportedVersions.add(SemVer.v2_7); supportedVersions.add(SemVer.v2_8); @@ -107,6 +110,7 @@ public WebserverAPI(Main main, String rid) { super(); this.main = main; this.rid = rid; + otelTelemetryWebHandler = new WebRequestTelemetryHandler(main); } public String getRID() { @@ -344,7 +348,7 @@ private String getConnectionUriDomain(HttpServletRequest req) throws ServletExce return null; } - private TenantIdentifier getTenantIdentifierWithoutVerifying(HttpServletRequest req) throws ServletException { + protected TenantIdentifier getTenantIdentifierWithoutVerifying(HttpServletRequest req) throws ServletException { return new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); } diff --git a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java index c0691863a..6010b1f64 100644 --- a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java @@ -17,6 +17,7 @@ package io.supertokens.webserver.api.core; import io.supertokens.Main; +import io.supertokens.pluginInterface.KeyValueInfo; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -29,6 +30,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; // the point of this API is only to test that the server is up and running. @@ -80,10 +82,21 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr try { AppIdentifier appIdentifier = getAppIdentifier(req); - Storage[] storages = StorageLayer.getStoragesForApp(main, - appIdentifier); // throws tenantOrAppNotFoundException + Storage[] storages = + otelTelemetryWebHandler.wrapInSpan(getTenantIdentifierWithoutVerifying(req), + "HelloAPI getStoragesForApp", + Map.of(), () -> { + try { + return StorageLayer.getStoragesForApp(main, + appIdentifier); // throws tenantOrAppNotFoundException + } catch (TenantOrAppNotFoundException e) { + throw new RuntimeException(e); + } + }); RateLimiter rateLimiter = RateLimiter.getInstance(appIdentifier, super.main, 200); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "HelloAPI handleRequest", + Map.of("rateLimited", String.valueOf(rateLimiter.checkRequest()))); if (!rateLimiter.checkRequest()) { if (Main.isTesting) { super.sendTextResponse(200, "RateLimitedHello", resp); @@ -96,7 +109,11 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr for (Storage storage : storages) { // even if the public tenant does not exist, the following function will return a null // idea here is to test that the storage is working - storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); + KeyValueInfo storageResponse = storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "HelloAPI storageTest", + Map.of("storageResponse", storageResponse == null ? "null" : storageResponse.value, + "createdTime", + storageResponse == null ? "null" : String.valueOf(storageResponse.createdAtTime))); } super.sendTextResponse(200, "Hello", resp); } catch (StorageQueryException | TenantOrAppNotFoundException e) { diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java index f8032715c..0779f8ddb 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java @@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; public class DashboardSignInAPI extends WebserverAPI { @@ -46,10 +47,14 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String email = InputParser.parseStringOrThrowError(input, "email", false); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "DashboardSignInAPI signIn", + Map.of("email", email)); + // normalize email email = Utils.normalizeAndValidateStringParam(email, "email"); email = io.supertokens.utils.Utils.normaliseEmail(email); @@ -63,6 +68,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String sessionId = Dashboard.signInDashboardUser( getAppIdentifier(req), enforcePublicTenantAndGetPublicTenantStorage(req), main, email, password); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "DashboardSignInAPI signIn", + Map.of("sessionId", sessionId)); + if (sessionId == null) { JsonObject response = new JsonObject(); response.addProperty("status", "INVALID_CREDENTIALS_ERROR"); @@ -72,6 +80,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("sessionId", sessionId); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardSignInAPI signIn response", + Map.of("status", response.get("status").getAsString(), "sessionId", sessionId)); super.sendJsonResponse(200, response, resp); } catch (UserSuspendedException e) { JsonObject response = new JsonObject(); @@ -80,6 +91,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I response.addProperty("message", "User is currently suspended, please sign in with another account, or reactivate the SuperTokens " + "core license key"); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardSignInAPI signIn response", + Map.of("status", response.get("status").getAsString(), "sessionId", + response.get("message").getAsString())); super.sendJsonResponse(200, response, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); From d720d69b466a8f09092a82c95f2b93cc69e5f2fa Mon Sep 17 00:00:00 2001 From: tamassoltesz Date: Fri, 29 Aug 2025 15:40:59 +0200 Subject: [PATCH 4/4] feat: refactor exception handling --- src/main/java/io/supertokens/Main.java | 3 + .../io/supertokens/dashboard/Dashboard.java | 55 +++++++++++++--- .../storageLayer/StorageLayer.java | 37 +++++++---- .../telemetry/TelemetryProvider.java | 5 +- .../telemetry/WebRequestTelemetryHandler.java | 19 ++++-- .../io/supertokens/webserver/PathRouter.java | 8 +-- .../supertokens/webserver/WebserverAPI.java | 2 +- .../webserver/api/core/HelloAPI.java | 30 ++++++--- .../api/dashboard/DashboardSignInAPI.java | 59 +++++++++++------ .../api/dashboard/DashboardUserAPI.java | 65 +++++++++++++++---- 10 files changed, 210 insertions(+), 73 deletions(-) diff --git a/src/main/java/io/supertokens/Main.java b/src/main/java/io/supertokens/Main.java index 307c3d965..f26b918ef 100644 --- a/src/main/java/io/supertokens/Main.java +++ b/src/main/java/io/supertokens/Main.java @@ -44,6 +44,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.telemetry.TelemetryProvider; +import io.supertokens.telemetry.WebRequestTelemetryHandler; import io.supertokens.version.Version; import io.supertokens.webserver.Webserver; import org.jetbrains.annotations.TestOnly; @@ -170,6 +171,8 @@ private void init() throws IOException, StorageQueryException { Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true); TelemetryProvider.initialize(this); + WebRequestTelemetryHandler.INSTANCE.initializeOtelProvider( + TelemetryProvider.getInstance(this)); //life would be much easier with DI.. // loading storage layer try { diff --git a/src/main/java/io/supertokens/dashboard/Dashboard.java b/src/main/java/io/supertokens/dashboard/Dashboard.java index 478de1621..bf29e876e 100644 --- a/src/main/java/io/supertokens/dashboard/Dashboard.java +++ b/src/main/java/io/supertokens/dashboard/Dashboard.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo; +import io.supertokens.pluginInterface.dashboard.DashboardStorage; import io.supertokens.pluginInterface.dashboard.DashboardUser; import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateUserIdException; @@ -34,16 +35,15 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.telemetry.WebRequestTelemetryHandler; import io.supertokens.utils.Utils; import jakarta.annotation.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.regex.Pattern; public class Dashboard { @@ -84,7 +84,24 @@ public static DashboardUser signUpDashboardUser(AppIdentifier appIdentifier, Sto } } - String hashedPassword = PasswordHashing.getInstance(main).createHashWithSalt(appIdentifier, password); + String hashedPassword = null; + try { + hashedPassword = WebRequestTelemetryHandler.INSTANCE.wrapInSpan(TenantIdentifier.BASE_TENANT, + "Hashing dashboard password", + Map.of("email", email), () -> + { + try { + return PasswordHashing.getInstance(main).createHashWithSalt(appIdentifier, password); + } catch (TenantOrAppNotFoundException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException e) { + if (e.getCause() instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.getCause(); + } + throw e; + } while (true) { String userId = Utils.getUUID(); @@ -92,10 +109,32 @@ public static DashboardUser signUpDashboardUser(AppIdentifier appIdentifier, Sto try { DashboardUser user = new DashboardUser(userId, email, hashedPassword, timeJoined); - StorageUtils.getDashboardStorage(storage).createNewDashboardUser(appIdentifier, user); + DashboardStorage dashboardStorage = StorageUtils.getDashboardStorage(storage); + WebRequestTelemetryHandler.INSTANCE.wrapInSpan(TenantIdentifier.BASE_TENANT, + "Creating user in dashboard storage", + Map.of("email", email), () -> { + try { + dashboardStorage.createNewDashboardUser(appIdentifier, user); + } catch (StorageQueryException | DuplicateUserIdException | DuplicateEmailException | + TenantOrAppNotFoundException e) { + throw new RuntimeException(e); + } + return null; + }); + return user; - } catch (DuplicateUserIdException ignored) { - // we retry with a new userId (while loop) + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof DuplicateUserIdException) { + // we retry with a new userId (while loop) + } else if (runtimeException.getCause() instanceof StorageQueryException) { + throw (StorageQueryException) runtimeException.getCause(); + } else if (runtimeException.getCause() instanceof DuplicateEmailException) { + throw (DuplicateEmailException) runtimeException.getCause(); + } else if (runtimeException.getCause() instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) runtimeException.getCause(); + } else { + throw runtimeException; + } } } } diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index 55f3fd80f..c7ed9aeab 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -38,6 +38,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.telemetry.TelemetryProvider; +import io.supertokens.telemetry.WebRequestTelemetryHandler; import io.supertokens.useridmapping.UserIdType; import jakarta.servlet.ServletException; import org.jetbrains.annotations.TestOnly; @@ -429,20 +430,32 @@ public static Storage[] getStoragesForApp(Main main, AppIdentifier appIdentifier throws TenantOrAppNotFoundException { Map userPoolToStorage = new HashMap<>(); - Map resources = - main.getResourceDistributor() - .getAllResourcesWithResourceKey(RESOURCE_KEY); - for (ResourceDistributor.KeyClass key : resources.keySet()) { - Storage storage = ((StorageLayer) resources.get(key)).storage; - if (key.getTenantIdentifier().toAppIdentifier().equals(appIdentifier)) { - userPoolToStorage.put(storage.getUserPoolId(), storage); + try { + return WebRequestTelemetryHandler.INSTANCE.wrapInSpan(appIdentifier.getAsPublicTenantIdentifier(), + "StorageLayer getStoragesForApp", + Map.of(), () -> { + Map resources = + main.getResourceDistributor() + .getAllResourcesWithResourceKey(RESOURCE_KEY); + for (ResourceDistributor.KeyClass key : resources.keySet()) { + Storage storage = ((StorageLayer) resources.get(key)).storage; + if (key.getTenantIdentifier().toAppIdentifier().equals(appIdentifier)) { + userPoolToStorage.put(storage.getUserPoolId(), storage); + } + } + Storage[] storages = userPoolToStorage.values().toArray(new Storage[0]); + if (storages.length == 0) { + throw new RuntimeException(new TenantOrAppNotFoundException(appIdentifier)); + } + return storages; + }); + } catch (RuntimeException e) { + if (e.getCause() instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.getCause(); + } else { + throw new RuntimeException(e); } } - Storage[] storages = userPoolToStorage.values().toArray(new Storage[0]); - if (storages.length == 0) { - throw new TenantOrAppNotFoundException(appIdentifier); - } - return storages; } public static StorageAndUserIdMapping findStorageAndUserIdMappingForUser( diff --git a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java index e035d2edc..ce57dbe11 100644 --- a/src/main/java/io/supertokens/telemetry/TelemetryProvider.java +++ b/src/main/java/io/supertokens/telemetry/TelemetryProvider.java @@ -101,7 +101,10 @@ public T wrapInSpanWithReturn(TenantIdentifier tenantIdentifier, String span Span span = spanBuilder.startSpan(); try (Scope scope = span.makeCurrent()) { - return (T) runnableWithOtel.runWithReturnValue(); + T returnValue = runnableWithOtel.runWithReturnValue(); + span.setAttribute("return.value.type", returnValue == null ? "null" : returnValue.getClass().getName()); + span.setAttribute("return.value.value", returnValue == null ? "null" : returnValue.toString()); + return returnValue; } finally { span.end(); } diff --git a/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java b/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java index 1804f7cf2..2ad99e447 100644 --- a/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java +++ b/src/main/java/io/supertokens/telemetry/WebRequestTelemetryHandler.java @@ -16,7 +16,6 @@ package io.supertokens.telemetry; -import io.supertokens.Main; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.opentelemetry.OtelProvider; import io.supertokens.pluginInterface.opentelemetry.RunnableWithOtel; @@ -26,15 +25,20 @@ import java.util.HashMap; import java.util.Map; -public class WebRequestTelemetryHandler { +public enum WebRequestTelemetryHandler { - private final OtelProvider telemetryProvider; + INSTANCE; - public WebRequestTelemetryHandler(Main main) { - this.telemetryProvider = TelemetryProvider.getInstance(main); + private OtelProvider telemetryProvider; + + public synchronized void initializeOtelProvider(OtelProvider otelProvider) { + if (this.telemetryProvider == null) { + this.telemetryProvider = otelProvider; + } } - public void wrapRequestInSpan(HttpServletRequest servletRequest, TenantIdentifier tenantIdentifier, RunnableWithOtel requestHandler) { + public void wrapRequestInSpan(HttpServletRequest servletRequest, TenantIdentifier tenantIdentifier, + RunnableWithOtel requestHandler) { Map requestAttributes = getRequestAttributes(servletRequest); telemetryProvider.wrapInSpanWithReturn(tenantIdentifier, "httpRequest", requestAttributes, requestHandler); } @@ -43,7 +47,8 @@ public void createSpan(TenantIdentifier tenantIdentifier, String name, Map T wrapInSpan(TenantIdentifier tenantIdentifier, String name, Map attributes, RunnableWithOtel runnable) { + public T wrapInSpan(TenantIdentifier tenantIdentifier, String name, Map attributes, + RunnableWithOtel runnable) { return (T) telemetryProvider.wrapInSpanWithReturn(tenantIdentifier, name, attributes, runnable); } diff --git a/src/main/java/io/supertokens/webserver/PathRouter.java b/src/main/java/io/supertokens/webserver/PathRouter.java index b8e7129bf..c0100ddb5 100644 --- a/src/main/java/io/supertokens/webserver/PathRouter.java +++ b/src/main/java/io/supertokens/webserver/PathRouter.java @@ -130,12 +130,12 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws } return null; }); - } catch (RuntimeException runtimeException) { - if (runtimeException.getCause() instanceof IOException) { - throw (IOException) runtimeException.getCause(); //unwrap the IOException so that it can be handled + } catch (RuntimeException exception) { + if (exception.getCause() instanceof IOException) { + throw (IOException) exception.getCause(); //unwrap the IOException so that it can be handled // properly by the caller } - throw runtimeException; + throw new RuntimeException(exception); } } } diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index 7554a13be..6f63257ce 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -110,7 +110,7 @@ public WebserverAPI(Main main, String rid) { super(); this.main = main; this.rid = rid; - otelTelemetryWebHandler = new WebRequestTelemetryHandler(main); + otelTelemetryWebHandler = WebRequestTelemetryHandler.INSTANCE; } public String getRID() { diff --git a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java index 6010b1f64..47d021969 100644 --- a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java @@ -21,6 +21,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.RateLimiter; @@ -79,9 +80,10 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO private void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific - try { AppIdentifier appIdentifier = getAppIdentifier(req); + TenantIdentifier nonVerifiedTenant = getTenantIdentifierWithoutVerifying(req); + Storage[] storages = otelTelemetryWebHandler.wrapInSpan(getTenantIdentifierWithoutVerifying(req), "HelloAPI getStoragesForApp", @@ -95,7 +97,7 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr }); RateLimiter rateLimiter = RateLimiter.getInstance(appIdentifier, super.main, 200); - otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "HelloAPI handleRequest", + otelTelemetryWebHandler.createSpan(nonVerifiedTenant, "HelloAPI handleRequest", Map.of("rateLimited", String.valueOf(rateLimiter.checkRequest()))); if (!rateLimiter.checkRequest()) { if (Main.isTesting) { @@ -109,16 +111,28 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr for (Storage storage : storages) { // even if the public tenant does not exist, the following function will return a null // idea here is to test that the storage is working - KeyValueInfo storageResponse = storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); - otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "HelloAPI storageTest", - Map.of("storageResponse", storageResponse == null ? "null" : storageResponse.value, - "createdTime", - storageResponse == null ? "null" : String.valueOf(storageResponse.createdAtTime))); + KeyValueInfo storageResponse = otelTelemetryWebHandler.wrapInSpan(nonVerifiedTenant, + "HelloAPI storageGetKeyValue", + Map.of(), () -> { + try { + return storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); + } catch (StorageQueryException e) { + throw new RuntimeException(e); + } + }); } super.sendTextResponse(200, "Hello", resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (TenantOrAppNotFoundException e) { // we send 500 status code throw new ServletException(e); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else if (e.getCause() instanceof StorageQueryException || + e.getCause() instanceof TenantOrAppNotFoundException) { + throw new ServletException(e.getCause()); + } + throw e; } } } diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java index 0779f8ddb..58745c3f2 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java @@ -65,15 +65,28 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I password = Utils.normalizeAndValidateStringParam(password, "password"); try { - String sessionId = Dashboard.signInDashboardUser( - getAppIdentifier(req), - enforcePublicTenantAndGetPublicTenantStorage(req), main, email, password); - otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), "DashboardSignInAPI signIn", - Map.of("sessionId", sessionId)); + String finalEmail = email; + String finalPassword = password; + String sessionId = otelTelemetryWebHandler.wrapInSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardSignInAPI signIn", + Map.of("email", email), () -> { + try { + return Dashboard.signInDashboardUser( + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), main, finalEmail, finalPassword); + } catch (StorageQueryException | UserSuspendedException | TenantOrAppNotFoundException | + ServletException | BadPermissionException e) { + throw new RuntimeException(e); + } + } + ); if (sessionId == null) { JsonObject response = new JsonObject(); response.addProperty("status", "INVALID_CREDENTIALS_ERROR"); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardSignInAPI signIn result", + Map.of("status", response.get("status").getAsString())); super.sendJsonResponse(200, response, resp); return; } @@ -84,21 +97,27 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I "DashboardSignInAPI signIn response", Map.of("status", response.get("status").getAsString(), "sessionId", sessionId)); super.sendJsonResponse(200, response, resp); - } catch (UserSuspendedException e) { - JsonObject response = new JsonObject(); - response.addProperty("status", "USER_SUSPENDED_ERROR"); - // TODO: update message - response.addProperty("message", - "User is currently suspended, please sign in with another account, or reactivate the SuperTokens " + - "core license key"); - otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), - "DashboardSignInAPI signIn response", - Map.of("status", response.get("status").getAsString(), "sessionId", - response.get("message").getAsString())); - super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { - throw new ServletException(e); + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof StorageQueryException + || runtimeException.getCause() instanceof TenantOrAppNotFoundException + || runtimeException.getCause() instanceof BadPermissionException) { + throw new ServletException(runtimeException.getCause()); + } else if (runtimeException.getCause() instanceof UserSuspendedException) { + JsonObject response = new JsonObject(); + response.addProperty("status", "USER_SUSPENDED_ERROR"); + // TODO: update message + response.addProperty("message", + "User is currently suspended, please sign in with another account, or reactivate the " + + "SuperTokens " + + "core license key"); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardSignInAPI signIn result", + Map.of("status", response.get("status").getAsString(), "message", + response.get("message").getAsString())); + super.sendJsonResponse(200, response, resp); + } else { + throw runtimeException; + } } - } } diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java index c66444b74..201d234ad 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java @@ -31,6 +31,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.Utils; @@ -41,6 +42,7 @@ import java.io.IOException; import java.io.Serial; +import java.util.Map; public class DashboardUserAPI extends WebserverAPI { @@ -61,6 +63,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // API is app specific try { + TenantIdentifier unverifiedTenantIdentifier = getTenantIdentifierWithoutVerifying(req); + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String email = InputParser.parseStringOrThrowError(input, "email", false); @@ -68,10 +72,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email = Utils.normalizeAndValidateStringParam(email, "email"); email = io.supertokens.utils.Utils.normaliseEmail(email); + otelTelemetryWebHandler.createSpan(unverifiedTenantIdentifier, "DashboardUserAPI", + Map.of("email", email)); + // check if input email is invalid if (!Dashboard.isValidEmail(email)) { JsonObject response = new JsonObject(); response.addProperty("status", "INVALID_EMAIL_ERROR"); + otelTelemetryWebHandler.createSpan(unverifiedTenantIdentifier, "DashboardUserAPI isValidEmail", + Map.of("response", response.get("status").getAsString())); super.sendJsonResponse(200, response, resp); return; } @@ -82,7 +91,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I password = Utils.normalizeAndValidateStringParam(password, "password"); // check if input password is a strong password - String passwordErrorMessage = Dashboard.validatePassword(password); + String finalPassword = password; + String passwordErrorMessage = + otelTelemetryWebHandler.wrapInSpan(unverifiedTenantIdentifier, "DashboardUserAPI validatePassword", + Map.of("email", email), () -> Dashboard.validatePassword(finalPassword)); if (passwordErrorMessage != null) { JsonObject response = new JsonObject(); response.addProperty("status", "PASSWORD_WEAK_ERROR"); @@ -91,24 +103,50 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I return; } - DashboardUser user = Dashboard.signUpDashboardUser( - getAppIdentifier(req), - enforcePublicTenantAndGetPublicTenantStorage(req), - main, email, password); + String finalEmail = email; + DashboardUser user = otelTelemetryWebHandler.wrapInSpan(unverifiedTenantIdentifier, + "DashboardUserAPI signUp", + Map.of("email", email), () -> + { + try { + return Dashboard.signUpDashboardUser( + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), + main, finalEmail, finalPassword); + } catch (StorageQueryException | DuplicateEmailException | FeatureNotEnabledException | + TenantOrAppNotFoundException | ServletException | BadPermissionException e) { + throw new RuntimeException(e); + } + } + ); JsonObject userAsJsonObject = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.add("user", userAsJsonObject); + otelTelemetryWebHandler.createSpan(unverifiedTenantIdentifier, "DashboardUserAPI signUp response", + Map.of("status", response.get("status").getAsString(), "userId", user.userId)); super.sendJsonResponse(200, response, resp); - } catch (DuplicateEmailException e) { - JsonObject response = new JsonObject(); - response.addProperty("status", "EMAIL_ALREADY_EXISTS_ERROR"); - super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | FeatureNotEnabledException | TenantOrAppNotFoundException | - BadPermissionException e) { - throw new ServletException(e); + } catch (RuntimeException e) { + if (e.getCause() instanceof DuplicateEmailException) { + JsonObject response = new JsonObject(); + response.addProperty("status", "EMAIL_ALREADY_EXISTS_ERROR"); + super.sendJsonResponse(200, response, resp); + } else if (e.getCause() instanceof StorageQueryException || + e.getCause() instanceof FeatureNotEnabledException || + e.getCause() instanceof TenantOrAppNotFoundException || + e.getCause() instanceof BadPermissionException) { + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardUserAPI doPost error", + Map.of("error", e.getCause().getMessage() == null ? "null" : e.getCause().getMessage())); + throw new ServletException(e.getCause()); + } else { + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardUserAPI doPost error", + Map.of("error", e.getMessage() == null ? "null" : e.getMessage())); + throw new RuntimeException(e); + } } } @@ -127,6 +165,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO if (!Dashboard.isValidEmail(newEmail)) { JsonObject response = new JsonObject(); response.addProperty("status", "INVALID_EMAIL_ERROR"); + otelTelemetryWebHandler.createSpan(getTenantIdentifierWithoutVerifying(req), + "DashboardUserAPI isValidEmail", + Map.of("email", newEmail, "response", response.get("status").getAsString())); super.sendJsonResponse(200, response, resp); return; }