Skip to content

Commit 87d3ff4

Browse files
authored
Merge pull request #2291 from digma-ai/fix-UndeclaredThrowableException-caught-null
analytics service exception handling Closes #2275
2 parents 8c679de + 0416e4a commit 87d3ff4

File tree

16 files changed

+133
-227
lines changed

16 files changed

+133
-227
lines changed

ide-common/src/main/java/org/digma/intellij/plugin/analytics/AnalyticsService.java

Lines changed: 42 additions & 104 deletions
Large diffs are not rendered by default.

ide-common/src/main/java/org/digma/intellij/plugin/analytics/AnalyticsServiceException.java

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import org.digma.intellij.plugin.common.ExceptionUtils;
44
import org.jetbrains.annotations.NotNull;
55

6-
import java.lang.reflect.InvocationTargetException;
7-
86
public class AnalyticsServiceException extends Exception {
97

108
public AnalyticsServiceException() {
@@ -27,7 +25,6 @@ public AnalyticsServiceException(String message, Throwable cause, boolean enable
2725
super(message, cause, enableSuppression, writableStackTrace);
2826
}
2927

30-
@NotNull
3128
public int getErrorCode(){
3229
AnalyticsProviderException analyticsProviderException = ExceptionUtils.findCause(AnalyticsProviderException.class, this);
3330
if (analyticsProviderException != null) {
@@ -39,28 +36,18 @@ public int getErrorCode(){
3936

4037

4138
@NotNull
42-
public String getMeaningfulMessage() {
39+
public String getNonNullMessage() {
4340

41+
if (getMessage() != null) {
42+
return getMessage();
43+
}
4444

4545
AnalyticsProviderException analyticsProviderException = ExceptionUtils.findCause(AnalyticsProviderException.class, this);
4646

47-
if (analyticsProviderException != null) {
48-
if (analyticsProviderException.getCause() != null && analyticsProviderException.getCause().getMessage() != null) {
49-
return analyticsProviderException.getCause().getMessage();
50-
} else {
51-
return analyticsProviderException.getMessage();
52-
}
53-
}
54-
55-
InvocationTargetException invocationTargetException = ExceptionUtils.findCause(InvocationTargetException.class, this);
56-
if (invocationTargetException != null) {
57-
if (invocationTargetException.getCause() != null && invocationTargetException.getCause().getMessage() != null) {
58-
return invocationTargetException.getCause().getMessage();
59-
} else if (invocationTargetException.getMessage() != null) {
60-
return invocationTargetException.getMessage();
61-
}
47+
if (analyticsProviderException != null && analyticsProviderException.getMessage() != null) {
48+
return analyticsProviderException.getMessage();
6249
}
6350

64-
return getMessage();
51+
return toString();
6552
}
6653
}

ide-common/src/main/java/org/digma/intellij/plugin/common/ExceptionUtils.java

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,68 @@
11
package org.digma.intellij.plugin.common;
22

3-
import org.digma.intellij.plugin.analytics.*;
3+
import org.digma.intellij.plugin.analytics.AnalyticsProviderException;
44
import org.jetbrains.annotations.*;
55

66
import javax.net.ssl.SSLException;
77
import java.io.*;
88
import java.lang.reflect.*;
99
import java.net.*;
10-
import java.net.http.HttpTimeoutException;
10+
import java.net.http.*;
11+
import java.util.Objects;
1112

1213
public class ExceptionUtils {
1314

1415
@Nullable
1516
public static <T extends Throwable> T findCause(@NotNull Class<T> toFind, @NotNull Throwable throwable) {
1617

1718
Throwable cause = throwable;
18-
while (cause != null && !toFind.isAssignableFrom(cause.getClass())) {
19+
while (cause != null && !toFind.equals(cause.getClass())) {
1920
cause = cause.getCause();
2021
}
2122

22-
return (T) cause;
23+
if (cause != null) {
24+
return toFind.cast(cause);
25+
}
26+
27+
return null;
2328
}
2429

25-
@Nullable
26-
public static <T> T find(@NotNull Exception e, @NotNull Class<T> javaClass) {
2730

28-
var ex = e.getCause();
29-
while (ex != null && !(javaClass.equals(ex.getClass()))) {
30-
ex = ex.getCause();
31-
}
32-
33-
if (ex != null) {
34-
return javaClass.cast(ex);
31+
//find the first exception that is not InvocationTargetException and not UndeclaredThrowableException
32+
@Nullable
33+
public static Throwable findFirstNonWrapperException(@NotNull Throwable throwable) {
34+
Throwable cause = throwable;
35+
while (cause instanceof InvocationTargetException || cause instanceof UndeclaredThrowableException) {
36+
cause = cause.getCause();
3537
}
3638

37-
return null;
39+
return cause;
3840
}
3941

40-
42+
@Nullable
4143
public static Throwable findFirstRealExceptionCause(@NotNull Throwable throwable) {
4244
Throwable cause = throwable;
43-
while (cause instanceof InvocationTargetException ||
44-
cause instanceof UndeclaredThrowableException) {
45+
while (cause instanceof InvocationTargetException || cause instanceof UndeclaredThrowableException) {
4546

46-
if (cause instanceof UndeclaredThrowableException undeclaredThrowableException) {
47-
cause = undeclaredThrowableException.getUndeclaredThrowable();
48-
} else if (cause instanceof InvocationTargetException invocationTargetException) {
49-
cause = invocationTargetException.getTargetException();
50-
}
51-
52-
if (cause instanceof AnalyticsProviderException && cause.getCause() != null) {
53-
cause = cause.getCause();
47+
cause = cause.getCause();
48+
//special treatment for AnalyticsProviderException, AnalyticsProviderException.cause may be null
49+
if (cause instanceof AnalyticsProviderException analyticsProviderException && analyticsProviderException.getCause() != null) {
50+
cause = analyticsProviderException.getCause();
5451
}
5552
}
5653

5754
return cause;
5855
}
5956

57+
@NotNull
6058
public static Class<? extends Throwable> findFirstRealExceptionCauseType(@NotNull Throwable throwable) {
61-
return findFirstRealExceptionCause(throwable).getClass();
59+
var realCause = findFirstRealExceptionCause(throwable);
60+
return Objects.requireNonNullElse(realCause, throwable).getClass();
6261
}
6362

6463

65-
public static String getFirstRealExceptionCauseTypeName(@NotNull Throwable throwable) {
64+
@NotNull
65+
public static String findFirstRealExceptionCauseTypeName(@NotNull Throwable throwable) {
6666
return findFirstRealExceptionCauseType(throwable).getName();
6767
}
6868

@@ -87,40 +87,44 @@ public static boolean isConnectionUnavailableException(@NotNull Throwable except
8787
//SocketTimeoutException and HttpTimeoutException are not considered connection unavailable.
8888
//but their derived classed may be. so compare equals and not instanceof
8989
if (SocketTimeoutException.class.equals(exception.getClass()) ||
90-
HttpTimeoutException.class.equals(exception.getClass())) {
90+
HttpTimeoutException.class.equals(exception.getClass()) ||
91+
isIOExceptionTimeout(exception)) {
9192
return false;
9293
}
9394

9495
return exception instanceof SocketException ||
9596
exception instanceof UnknownHostException ||
96-
exception instanceof HttpTimeoutException ||
97+
exception instanceof HttpConnectTimeoutException ||
9798
exception instanceof InterruptedIOException;
9899

99100
}
100101

101102

102-
public static boolean isSslConnectionException(@NotNull Throwable e) {
103+
private static boolean isIOExceptionTimeout(@NotNull Throwable exception) {
104+
return IOException.class.equals(exception.getClass()) &&
105+
exception.getMessage() != null &&
106+
exception.getMessage().toLowerCase().contains("timeout");
103107

104-
var ex = e.getCause();
105-
while (ex != null && !(ex instanceof SSLException)) {
106-
ex = ex.getCause();
107-
}
108-
return ex != null;
109108
}
110109

110+
111+
public static boolean isSslConnectionException(@NotNull Throwable e) {
112+
var cause = findCause(SSLException.class, e);
113+
return cause != null;
114+
}
115+
116+
@Nullable
111117
public static String getSslExceptionMessage(@NotNull Throwable e) {
112-
var ex = e.getCause();
113-
while (ex != null && !(ex instanceof SSLException)) {
114-
ex = ex.getCause();
115-
}
116-
if (ex != null) {
117-
return ex.getMessage();
118+
var cause = findCause(SSLException.class, e);
119+
if (cause != null) {
120+
return cause.getMessage();
118121
}
119122

120-
return e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
123+
return null;
121124
}
122125

123126

127+
@Nullable
124128
public static String getConnectExceptionMessage(@NotNull Throwable e) {
125129
var ex = e.getCause();
126130
while (ex != null && !(isConnectionUnavailableException(ex))) {
@@ -130,43 +134,32 @@ public static String getConnectExceptionMessage(@NotNull Throwable e) {
130134
return ex.getMessage();
131135
}
132136

133-
return e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
137+
return null;
134138
}
135139

136140

137141
public static boolean isEOFException(@NotNull Throwable e) {
138-
var ex = e.getCause();
139-
while (ex != null && !(ex instanceof EOFException)) {
140-
ex = ex.getCause();
141-
}
142-
return ex != null;
142+
return findCause(EOFException.class, e) != null;
143143
}
144144

145145

146-
@Nullable
146+
@NotNull
147147
public static String getNonEmptyMessage(@NotNull Throwable exception) {
148148

149-
if (exception instanceof AnalyticsServiceException e) {
150-
return e.getMeaningfulMessage();
149+
var realCause = findFirstRealExceptionCause(exception);
150+
if (realCause != null && realCause.getMessage() != null) {
151+
return realCause.getMessage();
151152
}
152153

153-
var exc = exception;
154154
var exceptionMessage = exception.getMessage();
155-
while ((exceptionMessage == null || exceptionMessage.isEmpty())
156-
&& exc != null) {
157-
158-
exc = exc.getCause();
159-
if (exc != null) {
160-
if (exc instanceof AnalyticsServiceException e) {
161-
return e.getMeaningfulMessage();
162-
} else {
163-
exceptionMessage = exc.getMessage();
164-
}
165-
}
155+
var cause = exception;
156+
while (cause != null && exceptionMessage == null) {
157+
exceptionMessage = cause.getMessage();
158+
cause = cause.getCause();
166159
}
167160

168161
if (exceptionMessage == null) {
169-
return exception.toString();
162+
exceptionMessage = exception.toString();
170163
}
171164

172165
return exceptionMessage;

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/AbstractLoginHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ abstract class AbstractLoginHandler(protected val analyticsProvider: RestAnalyti
1515

1616
protected val authApiClient = AuthApiClient(analyticsProvider)
1717

18-
protected fun defaultAccountExists(): Boolean {
18+
private fun defaultAccountExists(): Boolean {
1919
return DigmaDefaultAccountHolder.getInstance().account != null
2020
}
2121

@@ -56,7 +56,7 @@ abstract class AbstractLoginHandler(protected val analyticsProvider: RestAnalyti
5656
Log.warnWithException(logger, e, "Exception in login, url {}", analyticsProvider.apiUrl)
5757
ErrorReporter.getInstance().reportError("AuthManager.login", e)
5858
val errorMessage = ExceptionUtils.getNonEmptyMessage(e)
59-
reportPosthogEvent("login failed", mapOf("user" to user, "error" to errorMessage.toString()))
59+
reportPosthogEvent("login failed", mapOf("user" to user, "error" to errorMessage))
6060
//return no success LoginResult
6161
LoginResult(false, null, e.detailedMessage)
6262
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/AuthManager.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class AuthManager : Disposable {
5959
}
6060

6161

62-
6362
override fun dispose() {
6463
//nothing to do, used as parent disposable
6564
}
@@ -221,7 +220,7 @@ class AuthManager : Disposable {
221220

222221
} catch (e: InvocationTargetException) {
223222

224-
val authenticationException = ExceptionUtils.find(e, AuthenticationException::class.java)
223+
val authenticationException = ExceptionUtils.findCause(AuthenticationException::class.java, e)
225224
//log debug on AuthenticationException because it happens a lot, every time the token expires
226225
if (authenticationException == null) {
227226
Log.warnWithException(logger, e, "Exception in auth proxy {}", ExceptionUtils.getNonEmptyMessage(e))

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/CentralizedLoginHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class CentralizedLoginHandler(analyticsProvider: RestAnalyticsProvider) : Abstra
100100
//and user will be redirected to log in again
101101
if (e is AuthenticationException) {
102102
val errorMessage = ExceptionUtils.getNonEmptyMessage(e)
103-
reportPosthogEvent("loginOrRefresh failed", mapOf("error" to errorMessage.toString()))
103+
reportPosthogEvent("loginOrRefresh failed", mapOf("error" to errorMessage))
104104
logout()
105105
}
106106

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/LocalLoginHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class LocalLoginHandler(analyticsProvider: RestAnalyticsProvider) : AbstractLogi
120120
//and we'll do silent login on the next loginOrRefresh
121121
if (e is AuthenticationException) {
122122
val errorMessage = ExceptionUtils.getNonEmptyMessage(e)
123-
reportPosthogEvent("loginOrRefresh failed", mapOf("error" to errorMessage.toString()))
123+
reportPosthogEvent("loginOrRefresh failed", mapOf("error" to errorMessage))
124124
logout()
125125
}
126126

ide-common/src/main/kotlin/org/digma/intellij/plugin/errorreporting/ErrorReporter.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,6 @@ open class ErrorReporter {
183183
return
184184
}
185185

186-
//todo: change ActivityMonitor to application service so no need for project
187-
188186
val projectToUse = project ?: findActiveProject()
189187

190188
projectToUse?.let {

ide-common/src/main/kotlin/org/digma/intellij/plugin/posthog/ActivityMonitor.kt

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@ class ActivityMonitor(private val project: Project) : Disposable {
327327

328328
val details = mutableMapOf<String, Any>(
329329
"error.source" to "plugin",
330-
"action" to "unknown",
331330
"message" to message,
332331
"os.type" to osType,
333332
"ide.name" to ideName,
@@ -346,11 +345,9 @@ class ActivityMonitor(private val project: Project) : Disposable {
346345
stringWriter.toString()
347346
}
348347

349-
val exceptionMessage: String = it.let {
350-
ExceptionUtils.getNonEmptyMessage(it)
351-
} ?: ""
348+
val exceptionMessage: String = ExceptionUtils.getNonEmptyMessage(it)
352349

353-
val causeExceptionType = ExceptionUtils.getFirstRealExceptionCauseTypeName(it)
350+
val causeExceptionType = ExceptionUtils.findFirstRealExceptionCauseTypeName(it)
354351

355352
details["exception.message"] = exceptionMessage
356353
details["exception.stack-trace"] = exceptionStackTrace
@@ -397,11 +394,9 @@ class ActivityMonitor(private val project: Project) : Disposable {
397394
stringWriter.toString()
398395
}
399396

400-
val exceptionMessage: String = it.let {
401-
ExceptionUtils.getNonEmptyMessage(it)
402-
} ?: ""
397+
val exceptionMessage: String = ExceptionUtils.getNonEmptyMessage(it)
403398

404-
val causeExceptionType = ExceptionUtils.getFirstRealExceptionCauseTypeName(it)
399+
val causeExceptionType = ExceptionUtils.findFirstRealExceptionCauseTypeName(it)
405400

406401
details["exception.message"] = exceptionMessage
407402
details["exception.stack-trace"] = exceptionStackTrace
@@ -447,7 +442,7 @@ class ActivityMonitor(private val project: Project) : Disposable {
447442
val stringWriter = StringWriter()
448443
exception.printStackTrace(PrintWriter(stringWriter))
449444

450-
val exceptionMessage: String? = ExceptionUtils.getNonEmptyMessage(exception)
445+
val exceptionMessage: String = ExceptionUtils.getNonEmptyMessage(exception)
451446

452447
val eventName = if (isConnectionException) "connection error" else "analytics api error"
453448

@@ -458,8 +453,8 @@ class ActivityMonitor(private val project: Project) : Disposable {
458453
"apiMethodName" to methodName,
459454
"message" to message,
460455
"exception.type" to exception.javaClass.name,
461-
"cause.exception.type" to ExceptionUtils.getFirstRealExceptionCauseTypeName(exception),
462-
"exception.message" to exceptionMessage.toString(),
456+
"cause.exception.type" to ExceptionUtils.findFirstRealExceptionCauseTypeName(exception),
457+
"exception.message" to exceptionMessage,
463458
"exception.stack-trace" to stringWriter.toString(),
464459
"os.type" to osType,
465460
"ide.name" to ideName,
@@ -889,6 +884,7 @@ class ActivityMonitor(private val project: Project) : Disposable {
889884

890885
}
891886

887+
@Suppress("unused")
892888
fun registerAddEnvironment(environment: String) {
893889

894890
PersistenceService.getInstance().setEnvironmentAddedTimestamp()
@@ -989,5 +985,4 @@ class ActivityMonitor(private val project: Project) : Disposable {
989985
)
990986
}
991987

992-
993988
}

0 commit comments

Comments
 (0)