fr.pinguet62:reactor-call-stack

Parent pom providing dependency and plugin management for applications built with Maven

License

License

Categories

Categories

React User Interface Web Frameworks Reactor Container Microservices Reactive libraries
GroupId

GroupId

fr.pinguet62
ArtifactId

ArtifactId

reactor-call-stack
Last Version

Last Version

1.0.0
Release Date

Release Date

Type

Type

jar
Description

Description

Parent pom providing dependency and plugin management for applications built with Maven
Source Code Management

Source Code Management

https://github.com/pinguet62/reactor-call-stack

Download reactor-call-stack

How to add to project

<!-- https://jarcasting.com/artifacts/fr.pinguet62/reactor-call-stack/ -->
<dependency>
    <groupId>fr.pinguet62</groupId>
    <artifactId>reactor-call-stack</artifactId>
    <version>1.0.0</version>
</dependency>
// https://jarcasting.com/artifacts/fr.pinguet62/reactor-call-stack/
implementation 'fr.pinguet62:reactor-call-stack:1.0.0'
// https://jarcasting.com/artifacts/fr.pinguet62/reactor-call-stack/
implementation ("fr.pinguet62:reactor-call-stack:1.0.0")
'fr.pinguet62:reactor-call-stack:jar:1.0.0'
<dependency org="fr.pinguet62" name="reactor-call-stack" rev="1.0.0">
  <artifact name="reactor-call-stack" type="jar" />
</dependency>
@Grapes(
@Grab(group='fr.pinguet62', module='reactor-call-stack', version='1.0.0')
)
libraryDependencies += "fr.pinguet62" % "reactor-call-stack" % "1.0.0"
[fr.pinguet62/reactor-call-stack "1.0.0"]

Dependencies

compile (4)

Group / Artifact Type Version
io.projectreactor : reactor-core Optional jar
org.springframework : spring-context Optional jar
org.springframework : spring-aop Optional jar
org.aspectj : aspectjweaver Optional jar 1.9.6

provided (1)

Group / Artifact Type Version
org.projectlombok : lombok jar 1.18.16

test (2)

Group / Artifact Type Version
io.projectreactor : reactor-test jar
org.springframework.boot : spring-boot-starter-test jar 2.4.0

Project Modules

There are no modules declared in this project.

Reactor Call-Stack

https://github.com/pinguet62/reactor-call-stack/actions?workflow=CI

The need of "complete call stack hierarchy"

Problem of asynchronous

It's difficult to timing the run time of an asynchronous call.
This doesn't work, because the execution is triggered at subscription:

Mono<Object> getAsyncValue() {
    long start = System.currentTimeMillis();
    Mono<Object> result = callAsync();
    long end = System.currentTimeMillis();
    System.out.println(end - start); // 0ms
    return result;
}

A solution is to subscribe to start/end events of Publisher:

Mono<Object> getAsyncValue() {
    AtomicReference<Long> start = new AtomicReference<>();
    return callAsync()
        .doOnSubscribe(x -> start.set(System.currentTimeMillis()))
        .doOnTerminate(() -> {
            long end = System.currentTimeMillis();
            System.out.println(end - start.get());
        });
}

Need of call stack

Write calls individually in logs is not exploitable:

  • insufficient: you don't know what function triggered it (need of request id)
  • verbose: all calls are listed without order or hierarchy

A call stack like following is exploitable:

Total time: 620ms /productInfo
    ⮡ 608ms getProduct
        ⮡ 210ms callDatabase
        ⮡ 305ms callStockApi
    ⮡ 343ms getPrice

Toolbox

1. Register call

It's necessary to define specific sections of stack trace.

Transformer

Use appendCallStackToMono/appendCallStackToFlux transformers:

Mono<Object> getProduct() {
    return doSomething()
        .transform(Applicator.appendCallStackToMono("getProduct"));
}

Annotation & AOP

If you whant tag the entire method result, you can use Spring support with @LogTerminateTime:

@LogTerminateTime("getProduct")
Mono<Object> getProduct() {
    return doSomething();
}

2. Handle Publisher time execution

Use the appendCallStackToMono/appendCallStackToFlux transformers:

asyncCall()
    .transform(Handler.doWithCallStackFlux(callStack -> System.out.println(callStack)))

Full example

@RestController
class SampleController {
    Mono<Product> getProduct() {
        return doSomething1()
            .transform(Applicator.appendCallStackToMono("getProduct"));
    }
    Mono<Price> getPrice() {
        return doSomething2()
            .transform(Applicator.appendCallStackToMono("getPrice"));
    }
    
    @GetMapping
    Mono<Info> getInfo() {
        return Mono.zip(
            getProduct(), getPrice(),
            (price, product) -> new Info(product, price));
    }
}

class CallStackFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange)
                .transform(doWithCallStackMono(callStack -> System.out.println(callStack)));
    }
}
Total time: 620ms <root>
  ⮡ 608ms getProduct
  ⮡ 343ms getPrice

Versions

Version
1.0.0