Debugging

Examples:

fun main() {
    val seconds = LocalTime.now().second
    val source: Mono<Int>
    source = if (seconds % 2 == 0) {
        Flux.range(1, 10)
            .elementAt(5)
    } else if (seconds % 3 == 0) {
        Flux.range(0, 4)
            .elementAt(5)
    } else {
        Flux.just(1, 2, 3, 4)
            .elementAt(5)
    }

    source.block()
}

Errors:

log operator

log operator 를 활성화 시키면 다음과 같다.

fun main() {
    val seconds = LocalTime.now().second
    val source: Mono<Int>
    source = if (seconds % 2 == 0) {
        Flux.range(1, 10)
            .elementAt(5)
            .log("source A")
    } else if (seconds % 3 == 0) {
        Flux.range(0, 4)
            .elementAt(5)
            .log("source B")
    } else {
        Flux.just(1, 2, 3, 4)
            .elementAt(5)
            .log("source C")
    }

    source.block()
}

Errors:

21:21:48.728 [main] INFO source A -- | onSubscribe([Fuseable] MonoElementAt.ElementAtSubscriber)
21:21:48.730 [main] INFO source A -- | request(unbounded)
21:21:48.730 [main] INFO source A -- | onNext(6)
21:21:48.731 [main] INFO source A -- | onComplete()

log() operator 를 추가하면 추가한 지점의 Reactor Signal 을 출력한다.

Activate Debug Mode

Debug Mode 활성화:

  • Hooks.onOperatorDebug()
fun main() {
    Hooks.onOperatorDebug()
    val seconds = LocalTime.now().second
    val source: Mono<Int>
    source = if (seconds % 2 == 0) {
        Flux.range(1, 10)
            .elementAt(5)
            .log("source A")
    } else if (seconds % 3 == 0) {
        Flux.range(0, 4)
            .elementAt(5)
            .log("source B")
    } else {
        Flux.just(1, 2, 3, 4)
            .elementAt(5)
            .log("source C")
    }

    source.block()
}

Errors:

Debug Mode 를 활성화 시키면 Operator 체인상에서 에러가 발생한 지점을 정확히 가리킨다.

Hooks.onOperatorDebug() 는 애플리케이션 내에 있는 모든 Operator 들의 stacktrace 를 캡처(capture) 하기 때문에 비용이 많이 든다.

IntelliJ IDEA 에서 Reactor Sequence 활성화:

  • File > Settings
  • Languages & Frameworks > Reactive Streams
  • Debugger > Enable Reactor Debug Mode 는 체크 되어있고, Debug method initialization method 는 none
  • Hooks.onOperatorDebug() 를 선택

Checkpoint

fun main() {
    val seconds = LocalTime.now().second
    val source: Mono<Int>
    source = if (seconds % 2 == 0) {
        Flux.range(1, 10)
            .elementAt(5)
            .checkpoint("source range(1,10)")
    } else if (seconds % 3 == 0) {
        Flux.range(0, 4)
            .elementAt(5)
            .checkpoint("source range(0,4)")
    } else {
        Flux.just(1, 2, 3, 4)
            .elementAt(5)
            .checkpoint("source just(1,2,3,4)")
    }

    source.block()
}

Errors:

checkpoint 를 사용하면 코드베이스의 특정 지점에서만 어셈블리 추적 캡처를 활성화할 수 있다. 따라서 비용이 절감된다.

You can even do entirely without the filling of a stacktrace if you give the checkpoint a unique and meaningful name using checkpoint(String).

checkpoint 에 식별자를 포함하면 Traceback 을 생략하고 Description 을 통해 에러 발생 지점을 예상할 수 있다.

checkpoint(description, forceStackTrace) 를 통해서 Traceback 과 Description 을 모두 출력할 수 있다.

Assembly

Operator 들은 Mono or Flux 를 리턴하며, 체인을 형성한다. This declarative phase is called assembly time.

Traceback

디버그 모드를 활성화하면 Operator 의 Assembly 정보를 캡처(capture)하는데 이중에서 에러가 발생한 Operator 의 stacktrace 를 캡처한 Assembly 정보를 Traceback 이라고 한다.

Traceback 은 Suppressed Exceptions 형태로 원본 stacktrace 에 추가된다.

References

  • 스프링으로 시작하는 리액티브 프로그래밍 / 황정식 저 / 비제이퍼블릭