Skip to content

Commit bfbc9e1

Browse files
adinauergetsentry-botromtsn
authored
Crash the SDK on startup if mixed versions have been detected (#4277)
* Check for mixed SDK versions * Format code * format + api * Init noops if mixed versions detected * Add BuildConfig * config entries for agentless module sdk names * Add MANIFEST.MF for JARs * Throw on startup; use manifests for backend; reuse code for otel * Format code * Format code * changelog * api * changelog * Update sentry/src/main/java/io/sentry/util/InitUtil.java Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com> --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io> Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com>
1 parent 10dfe0c commit bfbc9e1

File tree

11 files changed

+287
-73
lines changed

11 files changed

+287
-73
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Unreleased
44

5+
### Behavioral Changes
6+
7+
- The Sentry SDK will now crash on startup if mixed versions have been detected ([#4277](https://github.com/getsentry/sentry-java/pull/4277))
8+
- On `Sentry.init` / `SentryAndroid.init` the SDK now checks if all Sentry Java / Android SDK dependencies have the same version.
9+
- While this may seem like a bad idea at first glance, mixing versions of dependencies has a very high chance of causing a crash later. We opted for a controlled crash that's hard to miss.
10+
- Note: This detection only works for new versions of the SDK, so please take this as a reminder to check your SDK version alignment manually when upgrading the SDK to this version and then you should be good.
11+
- The SDK will also print log messages if mixed versions have been detected at a later point. ([#4270](https://github.com/getsentry/sentry-java/pull/4270))
12+
- This takes care of cases missed by the startup check above due to older versions.
13+
514
### Features
615

716
- Increase http timeouts from 5s to 30s to have a better chance of events being delivered without retry ([#4276](https://github.com/getsentry/sentry-java/pull/4276))

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.pm.PackageInfo;
88
import io.sentry.DeduplicateMultithreadedEventProcessor;
99
import io.sentry.DefaultCompositePerformanceCollector;
10+
import io.sentry.DefaultVersionDetector;
1011
import io.sentry.IContinuousProfiler;
1112
import io.sentry.ILogger;
1213
import io.sentry.ISentryLifecycleToken;
@@ -15,6 +16,7 @@
1516
import io.sentry.NoOpConnectionStatusProvider;
1617
import io.sentry.NoOpContinuousProfiler;
1718
import io.sentry.NoOpTransactionProfiler;
19+
import io.sentry.NoopVersionDetector;
1820
import io.sentry.ScopeType;
1921
import io.sentry.SendFireAndForgetEnvelopeSender;
2022
import io.sentry.SendFireAndForgetOutboxSender;
@@ -198,6 +200,9 @@ static void initializeIntegrationsAndProcessors(
198200
if (options.getDebugMetaLoader() instanceof NoOpDebugMetaLoader) {
199201
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));
200202
}
203+
if (options.getVersionDetector() instanceof NoopVersionDetector) {
204+
options.setVersionDetector(new DefaultVersionDetector(options));
205+
}
201206

202207
final boolean isAndroidXScrollViewAvailable =
203208
loadClass.isClassAvailable("androidx.core.view.ScrollingView", options);

sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java

Lines changed: 9 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,11 @@
99
import io.sentry.Sentry;
1010
import io.sentry.SentryIntegrationPackageStorage;
1111
import io.sentry.SentryOptions;
12+
import io.sentry.internal.ManifestVersionReader;
1213
import io.sentry.protocol.SdkVersion;
1314
import io.sentry.protocol.SentryPackage;
14-
import java.io.IOException;
15-
import java.net.URL;
16-
import java.util.ArrayList;
17-
import java.util.Enumeration;
1815
import java.util.HashMap;
19-
import java.util.List;
2016
import java.util.Map;
21-
import java.util.jar.Attributes;
22-
import java.util.jar.Manifest;
2317
import org.jetbrains.annotations.NotNull;
2418
import org.jetbrains.annotations.Nullable;
2519

@@ -31,7 +25,8 @@ public final class SentryAutoConfigurationCustomizerProvider
3125
@Override
3226
public void customize(AutoConfigurationCustomizer autoConfiguration) {
3327
ensureSentryOtelStorageIsInitialized();
34-
final @Nullable VersionInfoHolder versionInfoHolder = createVersionInfo();
28+
final @Nullable ManifestVersionReader.VersionInfoHolder versionInfoHolder =
29+
ManifestVersionReader.getInstance().readOpenTelemetryVersion();
3530

3631
if (isSentryAutoInitEnabled()) {
3732
Sentry.init(
@@ -46,10 +41,10 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
4641
}
4742

4843
if (versionInfoHolder != null) {
49-
for (SentryPackage pkg : versionInfoHolder.packages) {
44+
for (SentryPackage pkg : versionInfoHolder.getPackages()) {
5045
SentryIntegrationPackageStorage.getInstance().addPackage(pkg.getName(), pkg.getVersion());
5146
}
52-
for (String integration : versionInfoHolder.integrations) {
47+
for (String integration : versionInfoHolder.getIntegrations()) {
5348
SentryIntegrationPackageStorage.getInstance().addIntegration(integration);
5449
}
5550
}
@@ -84,76 +79,21 @@ private boolean isSentryAutoInitEnabled() {
8479
}
8580
}
8681

87-
private @Nullable VersionInfoHolder createVersionInfo() {
88-
VersionInfoHolder infoHolder = null;
89-
try {
90-
final @NotNull Enumeration<URL> resources =
91-
ClassLoader.getSystemClassLoader().getResources("META-INF/MANIFEST.MF");
92-
while (resources.hasMoreElements()) {
93-
try {
94-
final @NotNull Manifest manifest = new Manifest(resources.nextElement().openStream());
95-
final @Nullable Attributes mainAttributes = manifest.getMainAttributes();
96-
if (mainAttributes != null) {
97-
final @Nullable String name = mainAttributes.getValue("Sentry-Opentelemetry-SDK-Name");
98-
final @Nullable String version = mainAttributes.getValue("Sentry-Version-Name");
99-
100-
if (name != null && version != null) {
101-
infoHolder = new VersionInfoHolder();
102-
infoHolder.sdkName = name;
103-
infoHolder.sdkVersion = version;
104-
infoHolder.packages.add(
105-
new SentryPackage("maven:io.sentry:sentry-opentelemetry-agent", version));
106-
final @Nullable String otelVersion =
107-
mainAttributes.getValue("Sentry-Opentelemetry-Version-Name");
108-
if (otelVersion != null) {
109-
infoHolder.packages.add(
110-
new SentryPackage("maven:io.opentelemetry:opentelemetry-sdk", otelVersion));
111-
infoHolder.integrations.add("OpenTelemetry");
112-
}
113-
final @Nullable String otelJavaagentVersion =
114-
mainAttributes.getValue("Sentry-Opentelemetry-Javaagent-Version-Name");
115-
if (otelJavaagentVersion != null) {
116-
infoHolder.packages.add(
117-
new SentryPackage(
118-
"maven:io.opentelemetry.javaagent:opentelemetry-javaagent",
119-
otelJavaagentVersion));
120-
infoHolder.integrations.add("OpenTelemetry-Agent");
121-
}
122-
break;
123-
}
124-
}
125-
} catch (Exception e) {
126-
// ignore
127-
}
128-
}
129-
} catch (IOException e) {
130-
// ignore
131-
}
132-
return infoHolder;
133-
}
134-
13582
private @Nullable SdkVersion createSdkVersion(
13683
final @NotNull SentryOptions sentryOptions,
137-
final @Nullable VersionInfoHolder versionInfoHolder) {
84+
final @Nullable ManifestVersionReader.VersionInfoHolder versionInfoHolder) {
13885
SdkVersion sdkVersion = sentryOptions.getSdkVersion();
13986

14087
if (versionInfoHolder != null
141-
&& versionInfoHolder.sdkName != null
142-
&& versionInfoHolder.sdkVersion != null) {
88+
&& versionInfoHolder.getSdkName() != null
89+
&& versionInfoHolder.getSdkVersion() != null) {
14390
sdkVersion =
14491
SdkVersion.updateSdkVersion(
145-
sdkVersion, versionInfoHolder.sdkName, versionInfoHolder.sdkVersion);
92+
sdkVersion, versionInfoHolder.getSdkName(), versionInfoHolder.getSdkVersion());
14693
}
14794
return sdkVersion;
14895
}
14996

150-
private static class VersionInfoHolder {
151-
private @Nullable String sdkName;
152-
private @Nullable String sdkVersion;
153-
private List<SentryPackage> packages = new ArrayList<>();
154-
private List<String> integrations = new ArrayList<>();
155-
}
156-
15797
private SdkTracerProviderBuilder configureSdkTracerProvider(
15898
SdkTracerProviderBuilder tracerProvider, ConfigProperties config) {
15999
return tracerProvider

sentry/api/sentry.api

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,11 @@ public final class io/sentry/DefaultSpanFactory : io/sentry/ISpanFactory {
413413
public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction;
414414
}
415415

416+
public final class io/sentry/DefaultVersionDetector : io/sentry/IVersionDetector {
417+
public fun <init> (Lio/sentry/SentryOptions;)V
418+
public fun checkForMixedVersions ()Z
419+
}
420+
416421
public final class io/sentry/DiagnosticLogger : io/sentry/ILogger {
417422
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/ILogger;)V
418423
public fun getLogger ()Lio/sentry/ILogger;
@@ -1098,6 +1103,10 @@ public abstract interface class io/sentry/ITransportFactory {
10981103
public abstract fun create (Lio/sentry/SentryOptions;Lio/sentry/RequestDetails;)Lio/sentry/transport/ITransport;
10991104
}
11001105

1106+
public abstract interface class io/sentry/IVersionDetector {
1107+
public abstract fun checkForMixedVersions ()Z
1108+
}
1109+
11011110
public final class io/sentry/InitPriority : java/lang/Enum {
11021111
public static final field HIGH Lio/sentry/InitPriority;
11031112
public static final field HIGHEST Lio/sentry/InitPriority;
@@ -1247,6 +1256,11 @@ public final class io/sentry/MainEventProcessor : io/sentry/EventProcessor, java
12471256
public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction;
12481257
}
12491258

1259+
public final class io/sentry/ManifestVersionDetector : io/sentry/IVersionDetector {
1260+
public fun <init> (Lio/sentry/SentryOptions;)V
1261+
public fun checkForMixedVersions ()Z
1262+
}
1263+
12501264
public abstract interface class io/sentry/MeasurementUnit {
12511265
public static final field NONE Ljava/lang/String;
12521266
public abstract fun apiName ()Ljava/lang/String;
@@ -1799,6 +1813,11 @@ public final class io/sentry/NoOpTransportFactory : io/sentry/ITransportFactory
17991813
public static fun getInstance ()Lio/sentry/NoOpTransportFactory;
18001814
}
18011815

1816+
public final class io/sentry/NoopVersionDetector : io/sentry/IVersionDetector {
1817+
public fun checkForMixedVersions ()Z
1818+
public static fun getInstance ()Lio/sentry/NoopVersionDetector;
1819+
}
1820+
18021821
public abstract interface class io/sentry/ObjectReader : java/io/Closeable {
18031822
public abstract fun beginArray ()V
18041823
public abstract fun beginObject ()V
@@ -3099,6 +3118,7 @@ public class io/sentry/SentryOptions {
30993118
public fun getTransactionProfiler ()Lio/sentry/ITransactionProfiler;
31003119
public fun getTransportFactory ()Lio/sentry/ITransportFactory;
31013120
public fun getTransportGate ()Lio/sentry/transport/ITransportGate;
3121+
public fun getVersionDetector ()Lio/sentry/IVersionDetector;
31023122
public final fun getViewHierarchyExporters ()Ljava/util/List;
31033123
public fun isAttachServerName ()Z
31043124
public fun isAttachStacktrace ()Z
@@ -3233,6 +3253,7 @@ public class io/sentry/SentryOptions {
32333253
public fun setTransactionProfiler (Lio/sentry/ITransactionProfiler;)V
32343254
public fun setTransportFactory (Lio/sentry/ITransportFactory;)V
32353255
public fun setTransportGate (Lio/sentry/transport/ITransportGate;)V
3256+
public fun setVersionDetector (Lio/sentry/IVersionDetector;)V
32363257
public fun setViewHierarchyExporters (Ljava/util/List;)V
32373258
}
32383259

@@ -4409,6 +4430,20 @@ public final class io/sentry/instrumentation/file/SentryFileWriter : java/io/Out
44094430
public fun <init> (Ljava/lang/String;Z)V
44104431
}
44114432

4433+
public final class io/sentry/internal/ManifestVersionReader {
4434+
public static fun getInstance ()Lio/sentry/internal/ManifestVersionReader;
4435+
public fun readManifestFiles ()V
4436+
public fun readOpenTelemetryVersion ()Lio/sentry/internal/ManifestVersionReader$VersionInfoHolder;
4437+
}
4438+
4439+
public final class io/sentry/internal/ManifestVersionReader$VersionInfoHolder {
4440+
public fun <init> ()V
4441+
public fun getIntegrations ()Ljava/util/List;
4442+
public fun getPackages ()Ljava/util/List;
4443+
public fun getSdkName ()Ljava/lang/String;
4444+
public fun getSdkVersion ()Ljava/lang/String;
4445+
}
4446+
44124447
public abstract interface class io/sentry/internal/debugmeta/IDebugMetaLoader {
44134448
public abstract fun loadDebugMeta ()Ljava/util/List;
44144449
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
@ApiStatus.Internal
7+
public final class DefaultVersionDetector implements IVersionDetector {
8+
9+
private final @NotNull SentryOptions options;
10+
11+
public DefaultVersionDetector(final @NotNull SentryOptions options) {
12+
this.options = options;
13+
}
14+
15+
@Override
16+
public boolean checkForMixedVersions() {
17+
return SentryIntegrationPackageStorage.getInstance().checkForMixedVersions(options.getLogger());
18+
}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.sentry;
2+
3+
public interface IVersionDetector {
4+
5+
/**
6+
* Checks whether all installed Sentry Java SDK dependencies have the same version.
7+
*
8+
* @return true if mixed versions have been detected, false if all versions align
9+
*/
10+
boolean checkForMixedVersions();
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.sentry;
2+
3+
import io.sentry.internal.ManifestVersionReader;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
@ApiStatus.Internal
8+
public final class ManifestVersionDetector implements IVersionDetector {
9+
10+
private final @NotNull SentryOptions options;
11+
12+
public ManifestVersionDetector(final @NotNull SentryOptions options) {
13+
this.options = options;
14+
}
15+
16+
@Override
17+
public boolean checkForMixedVersions() {
18+
ManifestVersionReader.getInstance().readManifestFiles();
19+
return SentryIntegrationPackageStorage.getInstance().checkForMixedVersions(options.getLogger());
20+
}
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.sentry;
2+
3+
public final class NoopVersionDetector implements IVersionDetector {
4+
5+
private static final NoopVersionDetector instance = new NoopVersionDetector();
6+
7+
private NoopVersionDetector() {}
8+
9+
public static NoopVersionDetector getInstance() {
10+
return instance;
11+
}
12+
13+
@Override
14+
public boolean checkForMixedVersions() {
15+
return false;
16+
}
17+
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,8 @@ public class SentryOptions {
538538
private @NotNull SentryReplayOptions sessionReplay;
539539

540540
@ApiStatus.Experimental private boolean captureOpenTelemetryEvents = false;
541+
542+
private @NotNull IVersionDetector versionDetector = NoopVersionDetector.getInstance();
541543
/**
542544
* Adds an event processor
543545
*
@@ -2541,6 +2543,17 @@ public void setEnableBackpressureHandling(final boolean enableBackpressureHandli
25412543
this.enableBackpressureHandling = enableBackpressureHandling;
25422544
}
25432545

2546+
@ApiStatus.Internal
2547+
@NotNull
2548+
public IVersionDetector getVersionDetector() {
2549+
return versionDetector;
2550+
}
2551+
2552+
@ApiStatus.Internal
2553+
public void setVersionDetector(final @NotNull IVersionDetector versionDetector) {
2554+
this.versionDetector = versionDetector;
2555+
}
2556+
25442557
/**
25452558
* Returns the rate the profiler will sample rates at. 100 hz means 100 traces in 1 second.
25462559
*

0 commit comments

Comments
 (0)