77import org .slf4j .Logger ;
88import org .slf4j .LoggerFactory ;
99
10+ import java .lang .Thread .UncaughtExceptionHandler ;
1011import java .net .Proxy ;
12+ import java .util .Collections ;
13+ import java .util .Date ;
14+ import java .util .Set ;
15+ import java .util .concurrent .RejectedExecutionHandler ;
16+ import java .util .concurrent .ScheduledThreadPoolExecutor ;
17+ import java .util .concurrent .ThreadPoolExecutor ;
18+ import java .util .concurrent .TimeUnit ;
1119
1220public class Bugsnag {
1321 private static final Logger LOGGER = LoggerFactory .getLogger (Bugsnag .class );
22+ private static final int SHUTDOWN_TIMEOUT_MS = 5000 ;
23+ private static final int SESSION_TRACKING_PERIOD_MS = 60000 ;
24+ private static final int CORE_POOL_SIZE = 1 ;
25+
26+ private ScheduledThreadPoolExecutor sessionExecutorService =
27+ new ScheduledThreadPoolExecutor (CORE_POOL_SIZE , new RejectedExecutionHandler () {
28+ @ Override
29+ public void rejectedExecution (Runnable runnable , ThreadPoolExecutor executor ) {
30+ LOGGER .error ("Rejected execution for sessionExecutorService" );
31+ }
32+ });
1433
1534 private Configuration config ;
35+ private final SessionTracker sessionTracker ;
1636
1737 //
1838 // Constructors
@@ -39,11 +59,45 @@ public Bugsnag(String apiKey, boolean sendUncaughtExceptions) {
3959 }
4060
4161 config = new Configuration (apiKey );
62+ sessionTracker = new SessionTracker (config );
4263
4364 // Automatically send unhandled exceptions to Bugsnag using this Bugsnag
4465 if (sendUncaughtExceptions ) {
4566 ExceptionHandler .enable (this );
4667 }
68+ addSessionTrackingShutdownHook ();
69+ scheduleSessionFlushes ();
70+ }
71+
72+ private void scheduleSessionFlushes () {
73+ sessionExecutorService .scheduleAtFixedRate (new Runnable () {
74+ @ Override
75+ public void run () {
76+ sessionTracker .flushSessions (new Date ());
77+ }
78+ }, SESSION_TRACKING_PERIOD_MS , SESSION_TRACKING_PERIOD_MS , TimeUnit .MILLISECONDS );
79+ }
80+
81+ private void addSessionTrackingShutdownHook () {
82+ Runtime .getRuntime ().addShutdownHook (new Thread () {
83+ @ Override
84+ public void run () {
85+ sessionTracker .shutdown ();
86+ sessionExecutorService .shutdown ();
87+ try {
88+ if (!sessionExecutorService
89+ .awaitTermination (SHUTDOWN_TIMEOUT_MS , TimeUnit .MILLISECONDS )) {
90+ LOGGER .warn ("Shutdown of 'session tracking' threads"
91+ + " took too long - forcing a shutdown" );
92+ sessionExecutorService .shutdownNow ();
93+ }
94+ } catch (InterruptedException ex ) {
95+ LOGGER .warn ("Shutdown of 'session tracking' thread "
96+ + "was interrupted - forcing a shutdown" );
97+ sessionExecutorService .shutdownNow ();
98+ }
99+ }
100+ });
47101 }
48102
49103 //
@@ -106,13 +160,30 @@ public void setDelivery(Delivery delivery) {
106160 config .delivery = delivery ;
107161 }
108162
163+
164+ /**
165+ * Set the method of delivery for Bugsnag sessions. By default we'll
166+ * send sessions asynchronously using a thread pool to
167+ * https://sessions.bugsnag.com, but you can override this to use a
168+ * different sending technique or endpoint (for example, if you are using
169+ * Bugsnag On-Premise).
170+ *
171+ * @param delivery the delivery mechanism to use
172+ * @see Delivery
173+ */
174+ public void setSessionDelivery (Delivery delivery ) {
175+ config .sessionDelivery = delivery ;
176+ }
177+
109178 /**
110179 * Set the endpoint to deliver Bugsnag errors report to. This is a convenient
111180 * shorthand for bugsnag.getDelivery().setEndpoint();
112181 *
113182 * @param endpoint the endpoint to send reports to
114183 * @see #setDelivery
184+ * @deprecated use {@link Configuration#setEndpoints(String, String)} instead
115185 */
186+ @ Deprecated
116187 public void setEndpoint (String endpoint ) {
117188 if (config .delivery instanceof HttpDelivery ) {
118189 ((HttpDelivery ) config .delivery ).setEndpoint (endpoint );
@@ -163,7 +234,7 @@ public void setProjectPackages(String... projectPackages) {
163234 }
164235
165236 /**
166- * Set a proxy to use when delivering Bugsnag error reports. This is a convenient
237+ * Set a proxy to use when delivering Bugsnag error reports and sessions . This is a convenient
167238 * shorthand for bugsnag.getDelivery().setProxy();
168239 *
169240 * @param proxy the proxy to use to send reports
@@ -172,6 +243,9 @@ public void setProxy(Proxy proxy) {
172243 if (config .delivery instanceof HttpDelivery ) {
173244 ((HttpDelivery ) config .delivery ).setProxy (proxy );
174245 }
246+ if (config .sessionDelivery instanceof HttpDelivery ) {
247+ ((HttpDelivery ) config .sessionDelivery ).setProxy (proxy );
248+ }
175249 }
176250
177251 /**
@@ -198,7 +272,7 @@ public void setSendThreads(boolean sendThreads) {
198272 }
199273
200274 /**
201- * Set a timeout (in ms) to use when delivering Bugsnag error reports.
275+ * Set a timeout (in ms) to use when delivering Bugsnag error reports and sessions .
202276 * This is a convenient shorthand for bugsnag.getDelivery().setTimeout();
203277 *
204278 * @param timeout the timeout to set (in ms)
@@ -208,6 +282,9 @@ public void setTimeout(int timeout) {
208282 if (config .delivery instanceof HttpDelivery ) {
209283 ((HttpDelivery ) config .delivery ).setTimeout (timeout );
210284 }
285+ if (config .sessionDelivery instanceof HttpDelivery ) {
286+ ((HttpDelivery ) config .sessionDelivery ).setTimeout (timeout );
287+ }
211288 }
212289
213290
@@ -369,17 +446,101 @@ public boolean notify(Report report, Callback reportCallback) {
369446 return false ;
370447 }
371448
449+ // increment session handled/unhandled count
450+ Session session = sessionTracker .getSession ();
451+
452+ if (session != null ) {
453+ if (report .getUnhandled ()) {
454+ session .incrementUnhandledCount ();
455+ } else {
456+ session .incrementHandledCount ();
457+ }
458+ report .setSession (session );
459+ }
460+
372461 // Build the notification
373462 Notification notification = new Notification (config , report );
374463
375464 // Deliver the notification
376465 LOGGER .debug ("Reporting error to Bugsnag" );
377466
378- config .delivery .deliver (config .serializer , notification );
467+ config .delivery .deliver (config .serializer , notification , config . getErrorApiHeaders () );
379468
380469 return true ;
381470 }
382471
472+ /**
473+ * Manually starts tracking a new session.
474+ *
475+ * Note: sessions are currently tracked on a per-thread basis. Therefore, if this method were
476+ * called from Thread A then Thread B, two sessions would be considered 'active'. Any custom
477+ * strategy used to track sessions should take this into account.
478+ *
479+ * Automatic session tracking can be enabled via
480+ * {@link Bugsnag#setAutoCaptureSessions(boolean)}, which will automatically create a new
481+ * session for each request
482+ */
483+ public void startSession () {
484+ sessionTracker .startSession (new Date (), false );
485+ }
486+
487+ /**
488+ * Sets whether or not Bugsnag should automatically capture and report User
489+ * sessions for each request.
490+ * <p>
491+ * By default this behavior is disabled.
492+ *
493+ * @param autoCaptureSessions whether sessions should be captured automatically
494+ */
495+ public void setAutoCaptureSessions (boolean autoCaptureSessions ) {
496+ config .setAutoCaptureSessions (autoCaptureSessions );
497+ }
498+
499+ /**
500+ * Retrieves whether or not Bugsnag should automatically capture
501+ * and report User sessions for each request.
502+ * @return whether sessions should be auto captured
503+ */
504+ public boolean shouldAutoCaptureSessions () {
505+ return config .shouldAutoCaptureSessions ();
506+ }
507+
508+ /**
509+ * Set the endpoint to deliver Bugsnag sessions to. This is a convenient
510+ * shorthand for bugsnag.getSessionDelivery().setEndpoint();
511+ *
512+ * @param endpoint the endpoint to send reports to
513+ * @see #setDelivery
514+ * @deprecated use {@link Configuration#setEndpoints(String, String)} instead
515+ */
516+ @ Deprecated
517+ public void setSessionEndpoint (String endpoint ) {
518+ if (config .sessionDelivery instanceof HttpDelivery ) {
519+ ((HttpDelivery ) config .sessionDelivery ).setEndpoint (endpoint );
520+ }
521+ }
522+
523+ /**
524+ * Set the endpoints to send data to. By default we'll send error reports to
525+ * https://notify.bugsnag.com, and sessions to https://sessions.bugsnag.com, but you can
526+ * override this if you are using Bugsnag Enterprise to point to your own Bugsnag endpoint.
527+ *
528+ * Please note that it is recommended that you set both endpoints. If the notify endpoint is
529+ * missing, an exception will be thrown. If the session endpoint is missing, a warning will be
530+ * logged and sessions will not be sent automatically.
531+ *
532+ * Note that if you are setting a custom {@link Delivery}, this method should be called after
533+ * the custom implementation has been set.
534+ *
535+ * @param notify the notify endpoint
536+ * @param sessions the sessions endpoint
537+ *
538+ * @throws IllegalArgumentException if the notify endpoint is empty or null
539+ */
540+ public void setEndpoints (String notify , String sessions ) throws IllegalArgumentException {
541+ config .setEndpoints (notify , sessions );
542+ }
543+
383544 /**
384545 * Close the connection to Bugsnag and unlink the exception handler.
385546 */
@@ -388,4 +549,19 @@ public void close() {
388549 config .delivery .close ();
389550 ExceptionHandler .disable (this );
390551 }
552+
553+ /**
554+ * Retrieves all instances of {@link Bugsnag} which are registered to
555+ * catch uncaught exceptions.
556+ *
557+ * @return clients which catch uncaught exceptions
558+ */
559+ public static Set <Bugsnag > uncaughtExceptionClients () {
560+ UncaughtExceptionHandler handler = Thread .getDefaultUncaughtExceptionHandler ();
561+ if (handler instanceof ExceptionHandler ) {
562+ ExceptionHandler bugsnagHandler = (ExceptionHandler )handler ;
563+ return Collections .unmodifiableSet (bugsnagHandler .uncaughtExceptionClients ());
564+ }
565+ return Collections .emptySet ();
566+ }
391567}
0 commit comments