MCPcopy Index your code
hub / github.com/Hakky54/log-captor

github.com/Hakky54/log-captor @v2.12.6

Chat with this repo
repository ↗ · DeepWiki ↗ · release v2.12.6 ↗ · + Follow
215 symbols 822 edges 42 files 45 documented · 21%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Actions Status Quality Gate Status Coverage JDK Compatibility Kotlin Compatibility Scala Compatibility Android API Compatibility Reliability Rating Security Rating Vulnerabilities Apache2 license Maven Central javadoc FOSSA Status Join the chat at https://gitter.im/hakky54/logcaptor Sponsor

SonarCloud

LogCaptor Tweet

Hey, hello there 👋 Welcome. I hope you will like this library

I have created this library with ❤️ and passion, mostly during evening and night hours. If you use my library and want to appreciate the work I have done, please consider to sponsor this project as a way to contribute back to the community. There are 3 options available to pick from: GitHub, Ko-fi and Open Collective

Install library with:

Install with maven

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>logcaptor</artifactId>
    <version>2.12.6</version>
    <scope>test</scope>
</dependency>

Install with Gradle

testImplementation 'io.github.hakky54:logcaptor:2.12.6'

Install with Scala SBT

libraryDependencies += "io.github.hakky54" % "logcaptor" % "2.12.6" % Test

Install with Apache Ivy

<dependency org="io.github.hakky54" name="logcaptor" rev="2.12.6" />

Table of contents

  1. Introduction
  2. Advantages
  3. Supported Java versions
  4. Tested Logging libraries
  5. Compatibility
  6. Usage
  7. Capture logs
  8. Reuse LogCaptor for multiple tests
  9. Capture logs for enabled logs only
  10. Capture exceptions within logs
  11. Capture Managed Diagnostic Context (MDC)
  12. Disable any logs for specific class
  13. Disable console output
  14. Capturing logs across different threads
  15. Returnable values from LogCaptor
  16. Known issues
  17. Using Log Captor alongside with other logging libraries
  18. Capturing logs of static inner classes
  19. Mixing up different classloaders
  20. Contributing
  21. License

Introduction

LogCaptor is a library which will enable you to easily capture logging entries for unit and integration testing purposes.

Do you want to capture the console output? Please have a look at ConsoleCaptor.

Advantages

  • No mocking required
  • No custom JUnit extension required
  • Plug & play

Supported Java versions

  • Java 8
  • Java 11+

Tested Logging libraries

  • SLF4J
  • Logback
  • Java Util Logging
  • Apache Log4j
  • Apache Log4j2
  • Log4j with Lombok
  • Log4j2 with Lombok
  • SLFJ4 with Lombok
  • JBossLog with Lombok
  • Java Util Logging with Lombok
  • Spring Boot Starter Log4j2
  • Google Flogger
  • Sude

See the unit test LogCaptorShould for all the scenario's or checkout this project Java Tutorials which contains more isolated examples of the individual logging frameworks

Compatibility

LogCaptor SLF4J Log4J Java Kotlin Scala Android
2.8.x 2.x.x 2.22.x 8+ 1.5+ 2.11+ 24+
2.7.x 1.x.x 2.21.x 8+ 1.5+ 2.11+ 24+

Usage

Capture logs
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class FooService {

    private static final Logger LOGGER = LogManager.getLogger(FooService.class);

    public void sayHello() {
        LOGGER.info("Keyboard not responding. Press any key to continue...");
        LOGGER.warn("Congratulations, you are pregnant!");
    }

}
Unit test:
import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;

public class FooServiceShould {

    @Test
    public void logInfoAndWarnMessages() {
        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);

        FooService fooService = new FooService();
        fooService.sayHello();

        // Get logs based on level
        assertThat(logCaptor.getInfoLogs()).containsExactly("Keyboard not responding. Press any key to continue...");
        assertThat(logCaptor.getWarnLogs()).containsExactly("Congratulations, you are pregnant!");

        // Get all logs
        assertThat(logCaptor.getLogs())
                .hasSize(2)
                .contains(
                    "Keyboard not responding. Press any key to continue...",
                    "Congratulations, you are pregnant!"
                );
    }
}
Initialize LogCaptor once and reuse it during multiple tests with clearLogs method within the afterEach method:
import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;

public class FooServiceShould {

    private static LogCaptor logCaptor;
    private static final String EXPECTED_INFO_MESSAGE = "Keyboard not responding. Press any key to continue...";
    private static final String EXPECTED_WARN_MESSAGE = "Congratulations, you are pregnant!";

    @BeforeAll
    public static void setupLogCaptor() {
        logCaptor = LogCaptor.forClass(FooService.class);
    }

    @AfterEach
    public void clearLogs() {
        logCaptor.clearLogs();
    }

    @AfterAll
    public static void tearDown() {
        logCaptor.close();
    }

    @Test
    public void logInfoAndWarnMessagesAndGetWithEnum() {
        FooService service = new FooService();
        service.sayHello();

        assertThat(logCaptor.getInfoLogs()).containsExactly(EXPECTED_INFO_MESSAGE);
        assertThat(logCaptor.getWarnLogs()).containsExactly(EXPECTED_WARN_MESSAGE);

        assertThat(logCaptor.getLogs()).hasSize(2);
    }

    @Test
    public void logInfoAndWarnMessagesAndGetWithString() {
        FooService service = new FooService();
        service.sayHello();

        assertThat(logCaptor.getInfoLogs()).containsExactly(EXPECTED_INFO_MESSAGE);
        assertThat(logCaptor.getWarnLogs()).containsExactly(EXPECTED_WARN_MESSAGE);

        assertThat(logCaptor.getLogs()).hasSize(2);
    }

}
Class which will log events if specific log level has been set
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class FooService {

    private static final Logger LOGGER = LogManager.getLogger(FooService.class);

    public void sayHello() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Keyboard not responding. Press any key to continue...");
        }
        LOGGER.info("Congratulations, you are pregnant!");
    }

}
Unit test:
import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;

public class FooServiceShould {

    @Test
    public void logInfoAndWarnMessages() {
        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);
        logCaptor.setLogLevelToInfo();

        FooService fooService = new FooService();
        fooService.sayHello();

        assertThat(logCaptor.getInfoLogs()).contains("Congratulations, you are pregnant!");
        assertThat(logCaptor.getDebugLogs())
            .doesNotContain("Keyboard not responding. Press any key to continue...")
            .isEmpty();
    }
}
Class which will also log an exception
import nl.altindag.log.service.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class FooService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ZooService.class);

    @Override
    public void sayHello() {
        try {
            tryToSpeak();
        } catch (IOException e) {
            LOGGER.error("Caught unexpected exception", e);
        }
    }

    private void tryToSpeak() throws IOException {
        throw new IOException("KABOOM!");
    }
}
Unit test:
import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.log.LogCaptor;
import nl.altindag.log.model.LogEvent;
import org.junit.jupiter.api.Test;

public class FooServiceShould {

    @Test
    void captureLoggingEventsContainingException() {
        LogCaptor logCaptor = LogCaptor.forClass(ZooService.class);

        FooService service = new FooService();
        service.sayHello();

        List<LogEvent> logEvents = logCaptor.getLogEvents();
        assertThat(logEvents).hasSize(1);

        LogEvent logEvent = logEvents.get(0);
        assertThat(logEvent.getMessage()).isEqualTo("Caught unexpected exception");
        assertThat(logEvent.getLevel()).isEqualTo("ERROR");
        assertThat(logEvent.getThrowable()).isPresent();

        assertThat(logEvent.getThrowable().get())
                .hasMessage("KABOOM!")
                .isInstanceOf(IOException.class);
    }
}
Capture Managed Diagnostic Context (MDC)
import nl.altindag.log.service.LogMessage;
import nl.altindag.log.service.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class FooService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceWithSlf4jAndMdcHeaders.class);

    public void sayHello() {
        try {
            MDC.put("my-mdc-key", "my-mdc-value");
            LOGGER.info(LogMessage.INFO.getMessage());
        } finally {
            MDC.clear();
        }

        LOGGER.info("Hello there!");
    }

}
Unit test:
import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.log.LogCaptor;
import nl.altindag.log.model.LogEvent;
import org.junit.jupiter.api.Test;

public class FooServiceShould {

   @Test
   void captureLoggingEventsContainingMdc() {
      LogCaptor logCaptor = LogCaptor.forClass(FooService.class);

      FooService service = new FooService();
      service.sayHello();

      List<LogEvent> logEvents = logCaptor.getLogEvents();

      assertThat(logEvents).hasSize(2);

      assertThat(logEvents.get(0).getDiagnosticContext())
              .hasSize(1)
              .extractingByKey("my-mdc-key")
              .isEqualTo("my-mdc-value");

      assertThat(logEvents.get(1).getDiagnosticContext()).isEmpty();
   }
}
Disable any logs for a specific class

In some use cases a unit test can generate too many logs by another class. Thi

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Method 165
Class 48
Enum 1
Interface 1

Languages

Java100%

Modules by API surface

src/test/java/nl/altindag/log/LogCaptorShould.java55 symbols
src/main/java/nl/altindag/log/LogCaptor.java32 symbols
src/main/java/nl/altindag/log/model/LogEvent.java14 symbols
src/main/java/nl/altindag/log/mapper/LogEventMapper.java8 symbols
src/main/java/nl/altindag/log/util/AppenderUtils.java7 symbols
src/test/java/nl/altindag/log/util/LogbackUtilsShould.java5 symbols
src/main/java/nl/altindag/log/model/LogMarker.java5 symbols
src/test/java/nl/altindag/log/spring/ConsoleOutputShould.java4 symbols
src/test/java/nl/altindag/log/service/LogMessage.java4 symbols
src/test/java/nl/altindag/log/ConsoleOutputShould.java4 symbols
src/main/java/nl/altindag/log/util/LogbackUtils.java4 symbols
src/main/java/nl/altindag/log/mapper/MarkerMapper.java4 symbols

For agents

$ claude mcp add log-captor \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page