Sample of code that generates mermaidjs sequence diagram. During the execution of the code, if a specific logger configuration is enabled, the program will output logs that can be interpreted as sequence diagram in mermaidjs.

Running tests

Run tests with mermaid output

Using this maven profile (mermaid), tests log output should show some mermaid code. You can then copy and paste it in https://mermaid.live/edit

mvn test -Pmermaid

Example of output :

[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running fr.baldir.UpdatingContactIbanTest

%% Updating contact's IBAN : when iban is valid

sequenceDiagram
autonumber
Front -->> MyApi : PATCH /00001/fiche-contact, <br/> contact id : 00001, iban : FR7600000000001
MyApi -->> Ficoba : POST https://ficobaUrl/validation-route <br/> iban : FR7600000000001
Ficoba -->> MyApi : FR7600000000001 exists
MyApi -->> Insurers : GET https://insurersUrl/contacts/00001
Insurers -->> MyApi : Informations assuré : 00001
MyApi -->> ContactsService : PUT /00001/fiche-contact
ContactsService -->> MyApi : 200 OK
MyApi -->> Front : 200 OK

%% Updating contact's IBAN : when iban is not found

sequenceDiagram
autonumber
Front -->> MyApi : PATCH /00001/fiche-contact, <br/> contact id : 00001, iban : FR7600000000000
MyApi -->> Ficoba : POST https://ficobaUrl/validation-route <br/> iban : FR7600000000000
Ficoba -->> MyApi : FR7600000000000 does not exist
MyApi -->> Front : 400 Bad Request
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.428 s - in fr.baldir.UpdatingContactIbanTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]

Run as regular tests

mvn test

Example of output

[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running fr.baldir.UpdatingContactIbanTest
20:07:05.258 [main] INFO  fr.baldir.UpdatingContactIbanTest -- Updating contact's IBAN : when iban is valid. Log visible in 'normal' test runs but not in 'mermaid' run
20:07:05.268 [main] INFO  fr.baldir.UpdatingContactIbanTest -- Updating contact's IBAN : when iban is not found. Log visible in 'normal' test runs but not in 'mermaid' run
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.155 s - in fr.baldir.UpdatingContactIbanTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]

Writing diagrams

Example of use

  1. Add a logger named "mermaid"

  2. trace before and after call to external service

Here, we traced in Insurances.java

public class Insurances {
    private final Logger mermaidLog = LoggerFactory.getLogger("mermaid");

    public InsuredDetails loadInsuredDetails(String contactId) {
        var insurersUrl = "https://insurersUrl/contacts/" + contactId;

        // Trace before remote call
        mermaidLog.trace("MyApi -->> Insurers : GET {}", insurersUrl);
        // simulate some API call to Insurers service
        var insuredDetails = new InsuredDetails(contactId);
        // Trace after remote call
        mermaidLog.trace("Insurers -->> MyApi : {}", insuredDetails.render());

        return insuredDetails;
    }

In tests, you can add diagram headers

@DisplayName("Updating contact's IBAN")
@IndicativeSentencesGeneration(separator = " : ", generator = DisplayNameGenerator.ReplaceUnderscores.class)
public class UpdatingContactIbanTest {
    Logger mermaidLog = LoggerFactory.getLogger("mermaid");

    private Logger log = LoggerFactory.getLogger(UpdatingContactIbanTest.class);
    private FicheContactController ficheContactController;

    @BeforeEach
    void setUp(TestInfo testInfo) {
        ficheContactController = createSut();

        log.info(testInfo.getDisplayName() + ". Log visible in 'normal' test runs but not in 'mermaid' run");

        // Some layout and diagram title as %% mermaid comment
        mermaidLog.trace("");
        mermaidLog.trace("%% " + testInfo.getDisplayName());
        mermaidLog.trace("");

        // Mermaid diagram headers and options
        mermaidLog.trace("sequenceDiagram");
        mermaidLog.trace("autonumber");
    }

Then, running tests will output desired diagrams.

    @Test
    void when_iban_is_valid() {
        var contactId = "00001";
        var iban = "FR7600000000001";

        ficheContactController.patchFicheContact(new FicheContactPatch(contactId, iban));
    }

Generated diagram code

%% Updating contact's IBAN : when iban is valid

sequenceDiagram
autonumber
Front -->> MyApi : PATCH /00001/fiche-contact, <br/> contact id : 00001, iban : FR7600000000001
MyApi -->> Ficoba : POST https://ficobaUrl/validation-route <br/> iban : FR7600000000001
Ficoba -->> MyApi : FR7600000000001 exists
MyApi -->> Insurers : GET https://insurersUrl/contacts/00001
Insurers -->> MyApi : Informations assuré : 00001
MyApi -->> ContactsService : PUT /00001/fiche-contact
ContactsService -->> MyApi : 200 OK
MyApi -->> Front : 200 OK

Rendered diagram

Diagram

Logging configuration

In this example we have 2 separate Logback logger configurations

  • A standard one : src/test/resources/logback-test.xml

  • A specific for mermaid diagram generation : src/test/resources/logback-mermaid.xml

logback-mermaid.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration
        xmlns="http://ch.qos.logback/xml/ns/logback"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback
        https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">

    <!--
        Ajouter cette propriété système à la commande java pour activer cette configuration spéciale
        -Dlogback.configurationFile=logback-mermaid.xml
    -->
    <appender name="Mermaid" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- We don't want noise in our diagrams, only the message -->
            <pattern>%msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
                <expression>return logger.equals("mermaid");</expression>
            </evaluator>
            <OnMismatch>DENY</OnMismatch>
            <OnMatch>ACCEPT</OnMatch>
        </filter>
    </appender>

    <root level="trace" >
        <appender-ref ref="Mermaid" />
    </root>
</configuration>

This logback-mermaid.xml

  • remove noise in output and only prints the raw message

  • only outputs logs from the logger called "mermaid"

    • to declare this logger in java code : Logger mermaidLog = LoggerFactory.getLogger("mermaid");

Why logging mermaid in "TRACE" log level?

Because in the standard logger, we may not want to output the mermaid logs. We could filter them out if we want, or enable them in production.