1+ /*
2+ * Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
3+ *
4+ * This software is licensed under the Apache License, Version 2.0 (the
5+ * "License") as published by the Apache Software Foundation.
6+ *
7+ * You may not use this file except in compliance with the License. You may
8+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+ * License for the specific language governing permissions and limitations
14+ * under the License.
15+ */
16+
17+ package io .supertokens .telemetry ;
18+
19+ import io .opentelemetry .api .GlobalOpenTelemetry ;
20+ import io .opentelemetry .api .OpenTelemetry ;
21+ import io .opentelemetry .api .common .Attributes ;
22+ import io .opentelemetry .api .trace .Span ;
23+ import io .opentelemetry .api .trace .propagation .W3CTraceContextPropagator ;
24+ import io .opentelemetry .context .Context ;
25+ import io .opentelemetry .context .propagation .ContextPropagators ;
26+ import io .opentelemetry .exporter .otlp .logs .OtlpGrpcLogRecordExporter ;
27+ import io .opentelemetry .exporter .otlp .trace .OtlpGrpcSpanExporter ;
28+ import io .opentelemetry .sdk .OpenTelemetrySdk ;
29+ import io .opentelemetry .sdk .logs .SdkLoggerProvider ;
30+ import io .opentelemetry .sdk .logs .export .BatchLogRecordProcessor ;
31+ import io .opentelemetry .sdk .resources .Resource ;
32+ import io .opentelemetry .sdk .trace .SdkTracerProvider ;
33+ import io .opentelemetry .sdk .trace .export .SimpleSpanProcessor ;
34+ import io .supertokens .Main ;
35+ import io .supertokens .ResourceDistributor ;
36+ import io .supertokens .config .Config ;
37+ import io .supertokens .pluginInterface .multitenancy .TenantIdentifier ;
38+ import io .supertokens .pluginInterface .multitenancy .exceptions .TenantOrAppNotFoundException ;
39+ import org .jetbrains .annotations .TestOnly ;
40+
41+ import java .util .concurrent .TimeUnit ;
42+
43+ import static io .opentelemetry .semconv .ServiceAttributes .SERVICE_NAME ;
44+
45+ public class TelemetryProvider extends ResourceDistributor .SingletonResource {
46+
47+ private static final String RESOURCE_ID = "io.supertokens.telemetry.TelemetryProvider" ;
48+
49+ private final OpenTelemetry openTelemetry ;
50+
51+ private static synchronized TelemetryProvider getInstance (Main main ) {
52+ TelemetryProvider instance = null ;
53+ try {
54+ instance = (TelemetryProvider ) main .getResourceDistributor ()
55+ .getResource (TenantIdentifier .BASE_TENANT , RESOURCE_ID );
56+ } catch (TenantOrAppNotFoundException ignored ) {
57+ }
58+ return instance ;
59+ }
60+
61+ public static void initialize (Main main ) {
62+ main .getResourceDistributor ()
63+ .setResource (TenantIdentifier .BASE_TENANT , RESOURCE_ID , new TelemetryProvider (main ));
64+ }
65+
66+ public static void createLogEvent (Main main , TenantIdentifier tenantIdentifier , String logMessage ,
67+ String logLevel ) {
68+ getInstance (main ).openTelemetry .getTracer ("core-tracer" )
69+ .spanBuilder (logLevel )
70+ .setParent (Context .current ())
71+ .setAttribute ("tenant.connectionUriDomain" , tenantIdentifier .getConnectionUriDomain ())
72+ .setAttribute ("tenant.appId" , tenantIdentifier .getAppId ())
73+ .setAttribute ("tenant.tenantId" , tenantIdentifier .getTenantId ())
74+ .startSpan ()
75+ .addEvent ("log" ,
76+ Attributes .builder ()
77+ .put ("message" , logMessage )
78+ .build (),
79+ System .currentTimeMillis (), TimeUnit .MILLISECONDS )
80+ .end ();
81+ }
82+
83+ public static Span startSpan (Main main , TenantIdentifier tenantIdentifier , String spanName ) {
84+ Span span = getInstance (main ).openTelemetry .getTracer ("core-tracer" )
85+ .spanBuilder (spanName )
86+ .setParent (Context .current ())
87+ .setAttribute ("tenant.connectionUriDomain" , tenantIdentifier .getConnectionUriDomain ())
88+ .setAttribute ("tenant.appId" , tenantIdentifier .getAppId ())
89+ .setAttribute ("tenant.tenantId" , tenantIdentifier .getTenantId ())
90+ .startSpan ();
91+
92+ span .makeCurrent (); // Set the span as the current context
93+ return span ;
94+ }
95+
96+ public static Span endSpan (Span span ) {
97+ if (span != null ) {
98+ span .end ();
99+ }
100+ return span ;
101+ }
102+
103+ public static Span addEventToSpan (Span span , String eventName , Attributes attributes ) {
104+ if (span != null ) {
105+ span .addEvent (eventName , attributes , System .currentTimeMillis (), TimeUnit .MILLISECONDS );
106+ }
107+ return span ;
108+ }
109+
110+
111+ private static OpenTelemetry initializeOpenTelemetry (Main main ) {
112+ if (getInstance (main ) != null && getInstance (main ).openTelemetry != null ) {
113+ return getInstance (main ).openTelemetry ; // already initialized
114+ }
115+
116+ Resource resource = Resource .getDefault ().toBuilder ()
117+ .put (SERVICE_NAME , "supertokens-core" )
118+ .build ();
119+
120+ String collectorUri = Config .getBaseConfig (main ).getOtelCollectorConnectionURI ();
121+
122+ SdkTracerProvider sdkTracerProvider =
123+ SdkTracerProvider .builder ()
124+ .setResource (resource )
125+ .addSpanProcessor (SimpleSpanProcessor .create (OtlpGrpcSpanExporter .builder ()
126+ .setEndpoint (collectorUri ) // otel collector
127+ .build ()))
128+ .build ();
129+
130+ OpenTelemetrySdk sdk =
131+ OpenTelemetrySdk .builder ()
132+ .setTracerProvider (sdkTracerProvider )
133+ .setPropagators (ContextPropagators .create (W3CTraceContextPropagator .getInstance ()))
134+ .setLoggerProvider (
135+ SdkLoggerProvider .builder ()
136+ .setResource (resource )
137+ .addLogRecordProcessor (
138+ BatchLogRecordProcessor .builder (
139+ OtlpGrpcLogRecordExporter .builder ()
140+ .setEndpoint (collectorUri )
141+ .build ())
142+
143+ .build ())
144+ .build ())
145+ .build ();
146+
147+ // Add hook to close SDK, which flushes logs
148+ Runtime .getRuntime ().addShutdownHook (new Thread (sdk ::close ));
149+ return sdk ;
150+ }
151+
152+ @ TestOnly
153+ public static void resetForTest () {
154+ GlobalOpenTelemetry .resetForTest ();
155+ }
156+
157+ public static void closeTelemetry (Main main ) {
158+ OpenTelemetry telemetry = getInstance (main ).openTelemetry ;
159+ if (telemetry instanceof OpenTelemetrySdk ) {
160+ ((OpenTelemetrySdk ) telemetry ).close ();
161+ }
162+ }
163+
164+ private TelemetryProvider (Main main ) {
165+ openTelemetry = initializeOpenTelemetry (main );
166+ }
167+ }
0 commit comments