11package org.digma.intellij.plugin.errorreporting
22
3- import com.github.benmanes.caffeine.cache.Caffeine
43import com.intellij.openapi.components.service
54import com.intellij.openapi.diagnostic.Logger
6- import com.intellij.openapi.diagnostic.UntraceableException
75import com.intellij.openapi.project.Project
86import net.bytebuddy.ByteBuddy
97import net.bytebuddy.description.method.MethodDescription
@@ -16,9 +14,8 @@ import org.digma.intellij.plugin.common.ExceptionUtils
1614import org.digma.intellij.plugin.common.findActiveProject
1715import org.digma.intellij.plugin.log.Log
1816import org.digma.intellij.plugin.posthog.ActivityMonitor
19- import java.lang.reflect.Field
20- import java.util.concurrent.TimeUnit
21- import java.util.concurrent.atomic.AtomicInteger
17+ import kotlin.time.Duration.Companion.minutes
18+ import kotlin.time.toJavaDuration
2219
2320
2421const val SEVERITY_PROP_NAME = " severity"
@@ -31,6 +28,7 @@ open class ErrorReporter {
3128
3229 private val logger: Logger = Logger .getInstance(this ::class .java)
3330
31+ private val frequentErrorDetector = FrequentErrorDetector (60 .minutes.toJavaDuration())
3432
3533 // must be public class
3634 class MyPauseInterceptor {
@@ -120,7 +118,7 @@ open class ErrorReporter {
120118 open fun reportError (project : Project ? , message : String , action : String , details : Map <String , String >) {
121119
122120
123- if (isTooFrequentError(message, action)) {
121+ if (frequentErrorDetector. isTooFrequentError(message, action)) {
124122 return
125123 }
126124
@@ -147,11 +145,11 @@ open class ErrorReporter {
147145 // many times the exception is no-connection exception, and that may happen too many times.
148146 // when there is no connection all timers will get an AnalyticsService exception every 10 seconds, it's useless
149147 // to report that. AnalyticsService exceptions are reported separately and will include no-connection exceptions.
150- if (ExceptionUtils .isConnectionException (throwable) || throwable is NoSelectedEnvironmentException ) {
148+ if (ExceptionUtils .isAnyConnectionException (throwable) || throwable is NoSelectedEnvironmentException ) {
151149 return
152150 }
153151
154- if (isTooFrequentException(message, throwable)) {
152+ if (frequentErrorDetector. isTooFrequentException(message, throwable)) {
155153 return
156154 }
157155
@@ -196,7 +194,7 @@ open class ErrorReporter {
196194 ) {
197195
198196 try {
199- if (isTooFrequentException(message, exception)) {
197+ if (frequentErrorDetector. isTooFrequentException(message, exception)) {
200198 return
201199 }
202200
@@ -219,7 +217,7 @@ open class ErrorReporter {
219217 }
220218
221219 open fun reportBackendError (project : Project ? , message : String , action : String ) {
222- if (isTooFrequentBackendError (message, action)) {
220+ if (frequentErrorDetector.isTooFrequentError (message, action)) {
223221 return
224222 }
225223
@@ -238,97 +236,19 @@ open class ErrorReporter {
238236 reportInternalFatalError(projectToUse, source, exception, details)
239237 }
240238
241- // this error should be reported only when its a fatal error that we must fix quickly.
239+ // this error should be reported only when it's a fatal error that we must fix quickly.
242240 // don't use it for all errors.
243241 // currently will be reported from EDT.assertNonDispatchThread and ReadActions.assertNotInReadAccess
244242 // which usually should be caught in development but if not, are very urgent to fix.
245- // if the error is not a result of an exception create a new RuntimeException and send it so we have the stack trace.
243+ // if the error is not a result of an exception create a new RuntimeException and send it, so we have the stack trace.
246244 open fun reportInternalFatalError (project : Project , source : String , exception : Throwable , details : Map <String , String > = mapOf()) {
247245
248- if (isTooFrequentException(source, exception)) {
246+ if (frequentErrorDetector. isTooFrequentException(source, exception)) {
249247 return
250248 }
251249
252250 ActivityMonitor .getInstance(project).registerInternalFatalError(source, exception, details)
253251
254252 }
255253
256-
257-
258- private fun isTooFrequentBackendError (message : String , action : String ): Boolean {
259- val counter = MyCache .getOrCreate(message, action)
260- val occurrences = counter.incrementAndGet()
261- return occurrences > 1
262- }
263-
264- private fun isTooFrequentError (message : String , action : String ): Boolean {
265- val counter = MyCache .getOrCreate(message, action)
266- val occurrences = counter.incrementAndGet()
267- return occurrences > 1
268- }
269-
270-
271- private fun isTooFrequentException (message : String , t : Throwable ): Boolean {
272- val hash = computeAccurateTraceHashCode(t)
273- val counter = MyCache .getOrCreate(hash, t, message)
274- val occurrences = counter.incrementAndGet()
275- return occurrences > 1
276- }
277-
278-
279- private fun computeAccurateTraceHashCode (throwable : Throwable ): Int {
280- val backtrace = getBacktrace(throwable)
281- if (backtrace == null ) {
282- val trace = if (throwable is UntraceableException ) null else throwable.stackTrace
283- return trace.contentHashCode()
284- }
285- return backtrace.contentDeepHashCode()
286- }
287-
288-
289- private fun getBacktrace (throwable : Throwable ): Array <Any >? {
290-
291- val backtrace = try {
292-
293- val backtraceField: Field ? = com.intellij.util.ReflectionUtil .getDeclaredField(Throwable ::class .java, " backtrace" )
294- if (backtraceField != null ) {
295- backtraceField.isAccessible = true
296- backtraceField.get(throwable)
297- } else {
298- null
299- }
300-
301- } catch (e: Throwable ) {
302- null
303- }
304-
305- if (backtrace != null && backtrace is Array <* > && backtrace.isArrayOf<Any >()) {
306- @Suppress(" UNCHECKED_CAST" )
307- return backtrace as Array <Any >
308- }
309-
310- return null
311-
312- }
313-
314- }
315-
316-
317- private object MyCache {
318- private val cache = Caffeine .newBuilder()
319- .maximumSize(1000 )
320- // expireAfterAccess means we don't send the error as long as it keeps occurring until it is quite for this time,
321- // if it reappears send it again
322- // .expireAfterAccess(10, TimeUnit.MINUTES)
323- // expireAfterWrite mean the error will be sent in fixed intervals.
324- .expireAfterWrite(24 , TimeUnit .HOURS )
325- .build<String , AtomicInteger >()
326-
327- fun getOrCreate (hash : Int , t : Throwable , message : String ): AtomicInteger {
328- return cache.get(" $hash :$t :$message " ) { AtomicInteger () }
329- }
330-
331- fun getOrCreate (message : String , action : String ): AtomicInteger {
332- return cache.get(" $message :$action " ) { AtomicInteger () }
333- }
334254}
0 commit comments