[android] RxJava 오류 발생 시 앱 강제종료 막고, 어디서 발생했는지 로그 찍기

RxJava를 쓰다보면 에러가 났는데, 어디서 났는지 모르는 로그가 찍히거나 앱이 강제종료되는 경우가 있다. 보통은 onError를 항상 추가해놓지만, 더 좋은 방법을 찾았다.

기본코드

Observable.timer(5, TimeUnit.SECONDS)
  .map { it -> null }
  .subscribe(
    { Log.i("APP#", it) }
  )
W/System.err: io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | java.lang.NullPointerException: The mapper function returned a null value.
W/System.err:     at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
W/System.err:     at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
W/System.err:     at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
W/System.err:     at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
W/System.err:     at io.reactivex.internal.observers.BasicFuseableObserver.fail(BasicFuseableObserver.java:110)
W/System.err:     at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
W/System.err:     at io.reactivex.internal.operators.observable.ObservableTimer$TimerObserver.run(ObservableTimer.java:67)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:38)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:26)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/System.err:     at java.lang.Thread.run(Thread.java:764)
W/System.err: Caused by: java.lang.NullPointerException: The mapper function returned a null value.
W/System.err:     at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
W/System.err:     at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
W/System.err:     ... 8 more

결과: 앱 강제종료 됨. 어디서 발생했는지 알 수 없음

onError 추가 시

Observable.timer(5, TimeUnit.SECONDS)
  .map { it -> null }
  .subscribe(
    { Log.i("APP#", it) },
    { Log.w("APP#", it) }
  )
W/APP#: java.lang.NullPointerException: The mapper function returned a null value.
        at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
        at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
        at io.reactivex.internal.operators.observable.ObservableTimer$TimerObserver.run(ObservableTimer.java:67)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:38)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:26)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

결과: 앱 강제종료 안됨. 어디서 발생했는지 알 수 없음.

RxJavaPlugins.setErrorHandler 추가 시

// application 클래스
RxJavaPlugins.setErrorHandler { Log.w("APP#", it) }
Observable.timer(5, TimeUnit.SECONDS)
  .map { it -> null }
  .subscribe(
    { Log.i("APP#", it) }
  )
W/APP#: io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | java.lang.NullPointerException: The mapper function returned a null value.
        at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
        at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
        at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
        at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
        at io.reactivex.internal.observers.BasicFuseableObserver.fail(BasicFuseableObserver.java:110)
        at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
        at io.reactivex.internal.operators.observable.ObservableTimer$TimerObserver.run(ObservableTimer.java:67)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:38)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:26)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.NullPointerException: The mapper function returned a null value.
        at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
        at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
        at io.reactivex.internal.operators.observable.ObservableTimer$TimerObserver.run(ObservableTimer.java:67) 
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:38) 
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:26) 
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
        at java.lang.Thread.run(Thread.java:764) 

결과: 앱 강제종료 안됨. 어디서 발생했는지 알 수 없음.

RxDogTag 추가 시

 // application 클래스
RxDogTag.install()
RxJavaPlugins.setErrorHandler { Log.w("APP#", it) }
Observable.timer(5, TimeUnit.SECONDS)
  .map { it -> null }
  .subscribe(
    { Log.i("APP#", it) }
  )
W/APP#: io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value.
    Caused by: java.lang.NullPointerException: The mapper function returned a null value.
        at kr.susemi99.testrxdogtag.MainActivity.test2(MainActivity.kt:29) <--- here
        at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
        at [[ ↓↓ Original trace ↓↓ ]].(:0)
        at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
        at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
        at io.reactivex.internal.operators.observable.ObservableTimer$TimerObserver.run(ObservableTimer.java:67)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:38)
        at io.reactivex.internal.schedulers.ScheduledDirectTask.call(ScheduledDirectTask.java:26)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

결과: 앱 강제종료 안됨. 어디서 발생했는지 알 수 있음

결론: RxDogTag와 RxJavaPlugins.setErrorHandler 를 같이 쓰면 된다.