LogCaptor lib consoleCaptor.getStandardOutput() doesn't show anything

Viewed 33

Using LogCaptor to unit test my logs, my issue is that with the same way as it is explained in the README I tried to use consoleCaptor.getStandardOutput() since I'm using @QuarkusTest but it gets nothing from the output, Even though the console is showing multiple json logs in the output.

@QuarkusTest
public class TestLog {

  ObjectMapper mapper = new ObjectMapper();

  @Test
  public void testLogIsInJsonFormat() {
    try (ConsoleCaptor consoleCaptor = new ConsoleCaptor()) {

      QueryClass query = new QueryClass();
      query.querySomeItem(null, null);
      List<String> standardOutPut = consoleCaptor.getStandardOutput(); // this list returns empty

      assertTrue(isValid(standardOutPut.get(1)));
    }
  }
}
{"timestamp":"2022-08-10T09:32:41.109+03:00","sequence":76,"loggerClassName":"org.jboss.logging.Logger","loggerName":"io.quarkus.amazon.lambda.runtime.AbstractLambdaPollLoop","level":"INFO","message":"Listening on: http://localhost:8081/_lambda_/2018-06-01/runtime/invocation/next","threadName":"Lambda Thread (TEST)","threadId":127,"mdc":{},"ndc":"","hostName":"my-laptop-name","processName":"JUnitStarter","processId":48118}

java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 0 // This means nothing was captured from the logs

My logger is @JBossLog, I suspect that maybe they are not compatible.

    <dependency>
      <groupId>io.github.hakky54</groupId>
      <artifactId>logcaptor</artifactId>
      <version>2.7.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.github.hakky54</groupId>
      <artifactId>consolecaptor</artifactId>
      <version>1.0.2</version>
      <scope>test</scope>
    </dependency>
1 Answers

I answered your question already here: https://github.com/Hakky54/log-captor/issues/37

But I will post the solution also here so others having the same issue can easily resolve it.

Quarkus is using a different classloader under the covers and that is the reason why it is not able to capture the logs. There is a workaround, but that only works without the @QuarkusTest annotation. If you need that annotation I would recommend to go with console-captor. See here for a working demo:

The steps below is for setting up console captor!

So what you need to do is:

  1. Add logback-classic as test dependency
  2. Add logback-test.xml as test resource
  3. Configure surefire plugin to exclude their slf4j logging implementation

So your pom.xml should be something like this:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${version.lombok}</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.logging</groupId>
        <artifactId>jboss-logging</artifactId>
        <version>${version.jboss-logging}</version>
    </dependency>

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-junit5</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${version.logback-classic}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.github.hakky54</groupId>
        <artifactId>consolecaptor</artifactId>
        <version>${version.consolecaptor}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>${version.junit}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>${version.junit}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>${version.assertj-core}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>${quarkus.platform.artifact-id}</artifactId>
            <version>${quarkus.platform.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${version.maven-surefire-plugin}</version>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExclude>org.jboss.slf4j:slf4j-jboss-logmanager</classpathDependencyExclude>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>
        <plugin>
            <groupId>${quarkus.platform.group-id}</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus.platform.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Your logback-test.xml file should have the following content:

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="TRACE">
        <appender-ref ref="STDOUT" />
    </root>

</configuration>

This is an example greeting resource:

import lombok.extern.jbosslog.JBossLog;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@JBossLog
@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        log.info("Saying hello from the logger!");
        return "Hello from RESTEasy Reactive";
    }

}

And the working test class:

import io.quarkus.test.junit.QuarkusTest;
import nl.altindag.console.ConsoleCaptor;
import org.junit.jupiter.api.Test;

import java.util.List;

import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;

@QuarkusTest
public class GreetingsResourceShould {

    @Test
    void successfullyCapturesLogs() {
        try(ConsoleCaptor consoleCaptor = new ConsoleCaptor()) {

            new GreetingResource().hello();

            List<String> standardOutput = consoleCaptor.getStandardOutput();
            assertThat(standardOutput).isNotEmpty();
            assertTrue(standardOutput.stream().anyMatch(message -> message.contains("Saying hello from the logger!")));
        }
    }

    @Test
    void successfullyCapturesLogsAlternativeWhileUsingRestAssured() {
        try(ConsoleCaptor consoleCaptor = new ConsoleCaptor()) {

            given()
                    .when().get("/hello")
                    .then()
                    .statusCode(200)
                    .body(is("Hello from RESTEasy Reactive"));

            List<String> standardOutput = consoleCaptor.getStandardOutput();
            assertThat(standardOutput).isNotEmpty();
            assertTrue(standardOutput.stream().anyMatch(message -> message.contains("Saying hello from the logger!")));
        }
    }

}

Can you give it a try and share your results here?

Related