{"id":25776916,"url":"https://github.com/Hakky54/log-captor","last_synced_at":"2026-08-13T13:30:21.157Z","repository":{"id":41889872,"uuid":"238128653","full_name":"Hakky54/log-captor","owner":"Hakky54","description":"🎯  LogCaptor captures log entries for unit and integration testing purposes","archived":false,"fork":false,"pushed_at":"2026-07-02T09:04:02.000Z","size":671,"stargazers_count":433,"open_issues_count":0,"forks_count":29,"subscribers_count":12,"default_branch":"master","last_synced_at":"2026-07-09T21:08:23.214Z","etag":null,"topics":["flogger","java","java-library","jboss","junit","log4j","log4j2","logging","lombok","slf4j","testing"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hakky54.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.MD","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["Hakky54"],"open_collective":"hakky54","ko_fi":"hakky54"}},"created_at":"2020-02-04T05:13:44.000Z","updated_at":"2026-07-06T08:18:30.000Z","dependencies_parsed_at":"2023-02-19T01:16:10.386Z","dependency_job_id":"3f2e8011-50ba-443d-86ef-29aa7caf7628","html_url":"https://github.com/Hakky54/log-captor","commit_stats":null,"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/Hakky54/log-captor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hakky54%2Flog-captor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hakky54%2Flog-captor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hakky54%2Flog-captor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hakky54%2Flog-captor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hakky54","download_url":"https://codeload.github.com/Hakky54/log-captor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hakky54%2Flog-captor/sbom","scorecard":{"id":60890,"data":{"date":"2025-08-11","repo":{"name":"github.com/Hakky54/log-captor","commit":"88e2e69d7f5af57b36aa48862ecab2cd0c7c91b3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/16 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"20 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/maven.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/maven.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/Hakky54/log-captor/maven.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/maven.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/Hakky54/log-captor/maven.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/maven.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/Hakky54/log-captor/maven.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/maven.yml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/Hakky54/log-captor/maven.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":1,"reason":"SAST tool is not run on all commits -- score normalized to 1","details":["Warn: 2 commits out of 16 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-15T01:40:47.077Z","repository_id":41889872,"created_at":"2025-08-15T01:40:47.077Z","updated_at":"2025-08-15T01:40:47.077Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36610019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-06T04:43:03.162Z","status":"online","status_checked_at":"2026-08-13T02:00:06.325Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flogger","java","java-library","jboss","junit","log4j","log4j2","logging","lombok","slf4j","testing"],"created_at":"2025-02-27T06:01:33.038Z","updated_at":"2026-08-13T13:30:21.151Z","avatar_url":"https://github.com/Hakky54.png","language":"Java","funding_links":["https://github.com/sponsors/Hakky54","https://opencollective.com/hakky54","https://ko-fi.com/hakky54"],"categories":["项目","测试","IV. Testing","java","Projects"],"sub_categories":["测试","1. Testing"],"readme":"[![Actions Status](https://github.com/Hakky54/log-captor/workflows/Build/badge.svg)](https://github.com/Hakky54/log-captor/actions)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=io.github.hakky54%3Alogcaptor\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=io.github.hakky54%3Alogcaptor\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n[![JDK Compatibility](https://img.shields.io/badge/JDK_Compatibility-8+-blue.svg)](#)\n[![Kotlin Compatibility](https://img.shields.io/badge/Kotlin_Compatibility-1.5+-blue.svg)](#)\n[![Scala Compatibility](https://img.shields.io/badge/Scala_Compatibility-2.11+-blue.svg)](#)\n[![Android API Compatibility](https://img.shields.io/badge/Android_API_Compatibility-24+-blue.svg)](#)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=io.github.hakky54%3Alogcaptor\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=io.github.hakky54%3Alogcaptor\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=io.github.hakky54%3Alogcaptor\u0026metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n[![Apache2 license](https://img.shields.io/badge/license-Aache2.0-blue.svg)](https://github.com/Hakky54/log-captor/blob/master/LICENSE)\n[![Maven](https://badges.mvnrepository.com/badge/io.github.hakky54/logcaptor/badge.svg?label=Maven)](https://mvnrepository.com/artifact/io.github.hakky54/logcaptor)\n[![Maven](https://badges.mvnrepository.com/badge/io.github.hakky54/logcaptor/badge.svg?type=rank\u0026label=Maven)](https://mvnrepository.com/artifact/io.github.hakky54/logcaptor)\n[![javadoc](https://javadoc.io/badge2/io.github.hakky54/logcaptor/javadoc.svg)](https://javadoc.io/doc/io.github.hakky54/logcaptor)\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FHakky54%2Flog-captor.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FHakky54%2Flog-captor?ref=badge_shield)\n[![Join the chat at https://gitter.im/hakky54/logcaptor](https://badges.gitter.im/hakky54/logcaptor.svg)](https://gitter.im/hakky54/logcaptor?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![Sponsor](https://img.shields.io/badge/sponsor-%F0%9F%92%96-pink)](https://github.com/sponsors/Hakky54)\n\n[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-white.svg)](https://sonarcloud.io/dashboard?id=io.github.hakky54%3Alogcaptor)\n\n# LogCaptor [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=With%20LogCaptor%20it%20is%20now%20very%20easy%20to%20captor%20and%20test%20your%20log%20message\u0026url=https://github.com/Hakky54/log-captor/\u0026via=hakky541\u0026hashtags=logging,testing,log4j,slf4j,log4j2,jul,lombok,developer,java,scala,kotlin,logcaptor)\n\n# Install library with:\n### Install with [maven](https://mvnrepository.com/artifact/io.github.hakky54/logcaptor)\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.hakky54\u003c/groupId\u003e\n    \u003cartifactId\u003elogcaptor\u003c/artifactId\u003e\n    \u003cversion\u003e2.12.6\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n### Install with Gradle\n```groovy\ntestImplementation 'io.github.hakky54:logcaptor:2.12.6'\n```\n### Install with Scala SBT\n```\nlibraryDependencies += \"io.github.hakky54\" % \"logcaptor\" % \"2.12.6\" % Test\n```\n### Install with Apache Ivy\n```xml\n\u003cdependency org=\"io.github.hakky54\" name=\"logcaptor\" rev=\"2.12.6\" /\u003e\n```\n\n## Table of contents\n1. [Introduction](#introduction)\n   - [Advantages](#advantages)\n   - [Supported Java versions](#supported-java-versions)\n   - [Tested Logging libraries](#tested-logging-libraries)\n   - [Compatibility](#compatibility)\n2. [Usage](#usage)\n   - [Capture logs](#capture-logs)\n   - [Reuse LogCaptor for multiple tests](#initialize-logcaptor-once-and-reuse-it-during-multiple-tests-with-clearlogs-method-within-the-aftereach-method)\n   - [Capture logs for enabled logs only](#class-which-will-log-events-if-specific-log-level-has-been-set)\n   - [Capture exceptions within logs](#class-which-will-also-log-an-exception)\n   - [Capture Managed Diagnostic Context (MDC)](#capture-managed-diagnostic-context-mdc)  \n   - [Disable any logs for specific class](#disable-any-logs-for-a-specific-class)   \n   - [Disable console output](#disable-console-output)\n   - [Capturing logs across different threads](#capturing-logs-across-different-threads)\n   - [Returnable values from LogCaptor](#returnable-values-from-logcaptor)\n3. [Known issues](#known-issues)\n   - [Using Log Captor alongside with other logging libraries](#using-log-captor-alongside-with-other-logging-libraries)\n   - [Capturing logs of static inner classes](#capturing-logs-of-static-inner-classes)\n   - [Mixing up different classloaders](#mixing-up-different-classloaders)\n4. [Contributing](#contributing)\n5. [License](#license)\n   \n\n# Introduction\nLogCaptor is a library which will enable you to easily capture logging entries for unit and integration testing purposes without the need of any custom Junit extentions, mocking or any other complex setup. It is plug \u0026 play, so you can start using it right away.\n\nDo you want to capture the console output? Please have a look at [ConsoleCaptor](https://github.com/Hakky54/console-captor).\n\n### Advantages\n- No mocking required\n- No custom JUnit extension required\n- Plug \u0026 play\n\n### Supported Java versions\n- Java 8\n- Java 11+\n\n### Tested Logging libraries\n - SLF4J\n - Logback\n - Java Util Logging  \n - Apache Log4j\n - Apache Log4j2  \n - Log4j with Lombok\n - Log4j2 with Lombok\n - SLFJ4 with Lombok\n - JBossLog with Lombok\n - Java Util Logging with Lombok\n - Spring Boot Starter Log4j2\n - Google Flogger\n - [Sude](https://github.com/Hakky54/sude)\n\nSee the unit test [LogCaptorShould](src/test/java/nl/altindag/log/LogCaptorShould.java) for all the scenario's or checkout this project [Java Tutorials](https://github.com/Hakky54/java-tutorials) which contains more isolated examples of the individual logging frameworks\n\n### Compatibility\n\n| LogCaptor | SLF4J | Log4J  | Java | Kotlin | Scala | Android |\n|:---------:|:-----:|:------:|:----:|:------:|-------|:-------:|\n|   2.8.x   | 2.x.x | 2.22.x |  8+  |  1.5+  | 2.11+ |   24+   |\n|   2.7.x   | 1.x.x | 2.21.x |  8+  |  1.5+  | 2.11+ |   24+   |\n\n# Usage\n##### Capture logs\n```java\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\n\npublic class FooService {\n\n    private static final Logger LOGGER = LogManager.getLogger(FooService.class);\n\n    public void sayHello() {\n        LOGGER.info(\"Keyboard not responding. Press any key to continue...\");\n        LOGGER.warn(\"Congratulations, you are pregnant!\");\n    }\n\n}\n```\n###### Unit test:\n```java\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport nl.altindag.log.LogCaptor;\nimport org.junit.jupiter.api.Test;\n\npublic class FooServiceShould {\n\n    @Test\n    public void logInfoAndWarnMessages() {\n        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);\n\n        FooService fooService = new FooService();\n        fooService.sayHello();\n\n        // Get logs based on level\n        assertThat(logCaptor.getInfoLogs()).containsExactly(\"Keyboard not responding. Press any key to continue...\");\n        assertThat(logCaptor.getWarnLogs()).containsExactly(\"Congratulations, you are pregnant!\");\n\n        // Get all logs\n        assertThat(logCaptor.getLogs())\n                .hasSize(2)\n                .contains(\n                    \"Keyboard not responding. Press any key to continue...\",\n                    \"Congratulations, you are pregnant!\"\n                );\n    }\n}\n```\n\n##### Initialize LogCaptor once and reuse it during multiple tests with clearLogs method within the afterEach method:\n```java\nimport nl.altindag.log.LogCaptor;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class FooServiceShould {\n\n    private static LogCaptor logCaptor;\n    private static final String EXPECTED_INFO_MESSAGE = \"Keyboard not responding. Press any key to continue...\";\n    private static final String EXPECTED_WARN_MESSAGE = \"Congratulations, you are pregnant!\";\n    \n    @BeforeAll\n    public static void setupLogCaptor() {\n        logCaptor = LogCaptor.forClass(FooService.class);\n    }\n\n    @AfterEach\n    public void clearLogs() {\n        logCaptor.clearLogs();\n    }\n    \n    @AfterAll\n    public static void tearDown() {\n        logCaptor.close();\n    }\n\n    @Test\n    public void logInfoAndWarnMessagesAndGetWithEnum() {\n        FooService service = new FooService();\n        service.sayHello();\n\n        assertThat(logCaptor.getInfoLogs()).containsExactly(EXPECTED_INFO_MESSAGE);\n        assertThat(logCaptor.getWarnLogs()).containsExactly(EXPECTED_WARN_MESSAGE);\n\n        assertThat(logCaptor.getLogs()).hasSize(2);\n    }\n\n    @Test\n    public void logInfoAndWarnMessagesAndGetWithString() {\n        FooService service = new FooService();\n        service.sayHello();\n\n        assertThat(logCaptor.getInfoLogs()).containsExactly(EXPECTED_INFO_MESSAGE);\n        assertThat(logCaptor.getWarnLogs()).containsExactly(EXPECTED_WARN_MESSAGE);\n\n        assertThat(logCaptor.getLogs()).hasSize(2);\n    }\n\n}\n```\n\n##### Class which will log events if specific log level has been set\n```java\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\n\npublic class FooService {\n\n    private static final Logger LOGGER = LogManager.getLogger(FooService.class);\n\n    public void sayHello() {\n        if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\"Keyboard not responding. Press any key to continue...\");\n        }\n        LOGGER.info(\"Congratulations, you are pregnant!\");\n    }\n\n}\n```\n###### Unit test:\n```java\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport nl.altindag.log.LogCaptor;\nimport org.junit.jupiter.api.Test;\n\npublic class FooServiceShould {\n\n    @Test\n    public void logInfoAndWarnMessages() {\n        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);\n        logCaptor.setLogLevelToInfo();\n\n        FooService fooService = new FooService();\n        fooService.sayHello();\n\n        assertThat(logCaptor.getInfoLogs()).contains(\"Congratulations, you are pregnant!\");\n        assertThat(logCaptor.getDebugLogs())\n            .doesNotContain(\"Keyboard not responding. Press any key to continue...\")\n            .isEmpty();\n    }\n}\n```\n\n##### Class which will also log an exception\n```java\nimport nl.altindag.log.service.Service;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\n\npublic class FooService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZooService.class);\n\n    @Override\n    public void sayHello() {\n        try {\n            tryToSpeak();\n        } catch (IOException e) {\n            LOGGER.error(\"Caught unexpected exception\", e);\n        }\n    }\n\n    private void tryToSpeak() throws IOException {\n        throw new IOException(\"KABOOM!\");\n    }\n}\n```\n###### Unit test:\n```java\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport nl.altindag.log.LogCaptor;\nimport nl.altindag.log.model.LogEvent;\nimport org.junit.jupiter.api.Test;\n\npublic class FooServiceShould {\n\n    @Test\n    void captureLoggingEventsContainingException() {\n        LogCaptor logCaptor = LogCaptor.forClass(ZooService.class);\n\n        FooService service = new FooService();\n        service.sayHello();\n\n        List\u003cLogEvent\u003e logEvents = logCaptor.getLogEvents();\n        assertThat(logEvents).hasSize(1);\n\n        LogEvent logEvent = logEvents.get(0);\n        assertThat(logEvent.getMessage()).isEqualTo(\"Caught unexpected exception\");\n        assertThat(logEvent.getLevel()).isEqualTo(\"ERROR\");\n        assertThat(logEvent.getThrowable()).isPresent();\n\n        assertThat(logEvent.getThrowable().get())\n                .hasMessage(\"KABOOM!\")\n                .isInstanceOf(IOException.class);\n    }\n}\n```\n##### Capture Managed Diagnostic Context (MDC)\n```java\nimport nl.altindag.log.service.LogMessage;\nimport nl.altindag.log.service.Service;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.MDC;\n\npublic class FooService {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceWithSlf4jAndMdcHeaders.class);\n\n    public void sayHello() {\n        try {\n            MDC.put(\"my-mdc-key\", \"my-mdc-value\");\n            LOGGER.info(LogMessage.INFO.getMessage());\n        } finally {\n            MDC.clear();\n        }\n\n        LOGGER.info(\"Hello there!\");\n    }\n\n}\n```\n###### Unit test:\n```java\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport nl.altindag.log.LogCaptor;\nimport nl.altindag.log.model.LogEvent;\nimport org.junit.jupiter.api.Test;\n\npublic class FooServiceShould {\n\n   @Test\n   void captureLoggingEventsContainingMdc() {\n      LogCaptor logCaptor = LogCaptor.forClass(FooService.class);\n\n      FooService service = new FooService();\n      service.sayHello();\n\n      List\u003cLogEvent\u003e logEvents = logCaptor.getLogEvents();\n\n      assertThat(logEvents).hasSize(2);\n\n      assertThat(logEvents.get(0).getDiagnosticContext())\n              .hasSize(1)\n              .extractingByKey(\"my-mdc-key\")\n              .isEqualTo(\"my-mdc-value\");\n\n      assertThat(logEvents.get(1).getDiagnosticContext()).isEmpty();\n   }\n}\n```\n\n##### Disable any logs for a specific class\nIn some use cases a unit test can generate too many logs by another class. This could be annoying as it will cause noise in your build logs. LogCaptor can disable those log messages with the following snippet:\n\n```java\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport nl.altindag.log.LogCaptor;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\npublic class FooServiceShould {\n\n    @AutoClose\n    private static LogCaptor logCaptorForSomeOtherService = LogCaptor.forClass(SomeService.class);\n\n    @BeforeAll\n    static void disableLogs() {\n        logCaptorForSomeOtherService.disableLogs();\n    }\n\n    @AfterAll\n    static void resetLogLevel() {\n        logCaptorForSomeOtherService.resetLogLevel();\n    }\n\n    @Test\n    void logInfoAndWarnMessages() {\n        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);\n\n        FooService service = new FooService();\n        service.sayHello();\n\n        assertThat(logCaptor.getLogs())\n                .hasSize(2)\n                .contains(\n                    \"Keyboard not responding. Press any key to continue...\",\n                    \"Congratulations, you are pregnant!\"\n                );\n    }\n}\n```\n\n##### Disable console output\nThe console output can be disabled with 2 different options:\n1. With the `LogCaptor.disableConsoleOutput()` method\n2. With a logback-test.xml file, see below for the details.\n\nAdd `logback-test.xml` to your test resources with the following content:\n```xml\n\u003cconfiguration\u003e\n   \u003cstatusListener class=\"ch.qos.logback.core.status.NopStatusListener\" /\u003e\n\u003c/configuration\u003e\n```\n\n##### Capturing logs across different threads\nLogCaptor is able to capture logs which are being logged from different threads. The main requirement to correctly capture those logs is to make sure the LogCaptor instance is created within the test method, so basically as a local variable and not as an instance or static variable.\n\n##### Returnable values from LogCaptor\n```java\nimport nl.altindag.log.LogCaptor;\nimport nl.altindag.log.model.LogEvent;\n\nimport java.time.ZonedDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\n\nclass FooServiceShould {\n\n   @Test\n   void showCaseAllReturnableValues() {\n        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);\n\n        List\u003cString\u003e logs = logCaptor.getLogs();\n        List\u003cString\u003e infoLogs = logCaptor.getInfoLogs();\n        List\u003cString\u003e debugLogs = logCaptor.getDebugLogs();\n        List\u003cString\u003e warnLogs = logCaptor.getWarnLogs();\n        List\u003cString\u003e errorLogs = logCaptor.getErrorLogs();\n        List\u003cString\u003e traceLogs = logCaptor.getTraceLogs();\n\n        LogEvent logEvent = logCaptor.getLogEvents().get(0);\n        String message = logEvent.getMessage();\n        String formattedMessage = logEvent.getFormattedMessage();\n        String level = logEvent.getLevel();\n        List\u003cObject\u003e arguments = logEvent.getArguments();\n        String loggerName = logEvent.getLoggerName();\n        String threadName = logEvent.getThreadName();\n        ZonedDateTime timeStamp = logEvent.getTimeStamp();\n        Map\u003cString, String\u003e diagnosticContext = logEvent.getDiagnosticContext();\n        Optional\u003cThrowable\u003e throwable = logEvent.getThrowable();\n    }\n    \n}\n```\n\n# Known issues\n## Using Log Captor alongside with other logging libraries\nWhen building your maven or gradle project it can complain that you are using multiple SLF4J implementations. Log Captor is using logback as SLF4J implementation and SLF4J doesn't allow you to use multiple implementations, therefore you need to explicitly specify which to use during which build phase if you are using multiple SLF4J implementations.\n\nDuring the test execution it can give you the following warning:\n```\nSLF4J: Class path contains multiple SLF4J bindings.\nSLF4J: Found binding in [jar:file:~/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.17.0/log4j-slf4j-impl-2.17.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]\nSLF4J: Found binding in [jar:file:~/.m2/repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]\nSLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.\nSLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]\n```\n\nBecause of this dependency issue the test may fail to capture the logs. You can fix that by excluding your main logging framework during the unit/integration test phase.\nBelow is an example for Maven Failsafe and Maven Surefire. You can discover which dependency you need to exclude by anaylising the SLF4J warning displayed above.\n```xml\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-surefire-plugin\u003c/artifactId\u003e\n            \u003cconfiguration\u003e\n                \u003cclasspathDependencyExcludes\u003e\n                    \u003cclasspathDependencyExclude\u003eorg.apache.logging.log4j:log4j-slf4j-impl\u003c/classpathDependencyExclude\u003e\n                    \u003cclasspathDependencyExclude\u003eorg.apache.logging.log4j:log4j-slf4j2-impl\u003c/classpathDependencyExclude\u003e\n                \u003c/classpathDependencyExcludes\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-failsafe-plugin\u003c/artifactId\u003e\n            \u003cconfiguration\u003e\n                \u003cclasspathDependencyExcludes\u003e\n                    \u003cclasspathDependencyExclude\u003eorg.apache.logging.log4j:log4j-slf4j-impl\u003c/classpathDependencyExclude\u003e\n                    \u003cclasspathDependencyExclude\u003eorg.apache.logging.log4j:log4j-slf4j2-impl\u003c/classpathDependencyExclude\u003e\n                \u003c/classpathDependencyExcludes\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n``` \nAnd for gradle:\n```groovy\nconfigurations {\n    testImplementation {\n        exclude(group = \"org.apache.logging.log4j\", module = \"log4j-slf4j-impl\")\n        exclude(group = \"org.apache.logging.log4j\", module = \"log4j-slf4j2-impl\")\n    }\n}\n```\n\n## Capturing logs of static inner classes\nLogCaptor successfully captures logs of static inner classes with `LogCaptor.forClass(MyStaticInnerClass.class)` construction \nwhen SLF4J, Log4J, JUL is used. This doesn't apply to static inner classes when Log4J2 is used, because it uses different method to for initializing the logger. \nIt used `Class.getCanonicalName()` under the covers instead of `Class.getName()`. You should use `LogCaptor.forName(StaticInnerClass.class.getCanonicalName())` or `LogCaptor.forRoot()`for to be able to capture logs for static inner classes while using Log4J2.\n\n## Mixing up different classloaders\nIt may occur that different classloaders are being used during your tests. LogCaptor works with different classloaders, but it will fail to set up if part of it has been created with a different classloader.\nThis may occur for example while using `@QuarkusTest` annotation. The logger will be setup by the default JDK classloader `[jdk.internal.loader.ClassLoaders$AppClassLoader]` while LogCaptor will be setup by the other classloader, in this case Quarkus `[io.quarkus.bootstrap.classloading.QuarkusClassLoader]`. \nLogCaptor will try to cast an object during the preparation, but it will fail as it is not possible to cast an object created by a different classloader. You need to make sure the logger is using the same classloader as LogCaptor or the other way around.\n\nThere is also an easier alternative solution by sending all of your logs to the console and capture that with [ConsoleCaptor](https://github.com/Hakky54/console-captor).\nAdd the following two dependencies to your project:\n```xml\n\u003cdependencies\u003e\n   \u003cdependency\u003e\n      \u003cgroupId\u003eio.github.hakky54\u003c/groupId\u003e\n      \u003cartifactId\u003econsolecaptor\u003c/artifactId\u003e\n      \u003cscope\u003etest\u003c/scope\u003e\n   \u003c/dependency\u003e\n\n   \u003cdependency\u003e\n      \u003cgroupId\u003ech.qos.logback\u003c/groupId\u003e\n      \u003cartifactId\u003elogback-classic\u003c/artifactId\u003e\n      \u003cscope\u003etest\u003c/scope\u003e\n   \u003c/dependency\u003e\n\n\u003c/dependencies\u003e\n```\n\nAdd the `logback-test.xml` configuration below to your test resources:\n```xml\n\u003cconfiguration\u003e\n\n    \u003cappender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\"\u003e\n        \u003cencoder\u003e\n            \u003cpattern\u003e%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\u003c/pattern\u003e\n        \u003c/encoder\u003e\n    \u003c/appender\u003e\n\n    \u003croot level=\"TRACE\"\u003e\n        \u003cappender-ref ref=\"STDOUT\" /\u003e\n    \u003c/root\u003e\n\n\u003c/configuration\u003e\n```\n\nYour target class:\n```java\n@Path(\"/hello\")\npublic class HelloResource {\n    \n    Logger logger = LoggerFactory.getLogger(GreetingResource.class);\n\n    @GET\n    @Produces(MediaType.TEXT_PLAIN)\n    public String hello() {\n        logger.info(\"Hello\");\n        return \"Hello\";\n    }\n}\n```\nYour test class:\n```java\nimport io.quarkus.test.junit.QuarkusTest;\nimport nl.altindag.console.ConsoleCaptor;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@QuarkusTest\nclass QuarkusTestTest {\n\n    @Test\n    void captureLogs() {\n        try(ConsoleCaptor consoleCaptor = new ConsoleCaptor()) {\n           HelloResource resource = new HelloResource();\n           resource.hello();\n\n           List\u003cString\u003e standardOutput = consoleCaptor.getStandardOutput();\n\n           assertThat(standardOutput)\n                   .hasSize(1)\n                   .contains(\"Hello\");\n        }\n    }\n\n}\n```\n\n## Support\n\nLogCaptor needs your help!\n\nIf you can, please consider sponsoring LogCaptor. Even a small donation would help us offset the recurring maintenance costs. With enough sponsors we would be able to make LogCaptor grow faster and stronger! You can sponsor it at [GitHub](https://github.com/sponsors/Hakky54), [Ko-fi](https://ko-fi.com/hakky54) or [open collective](https://opencollective.com/hakky54).\n\nIf you are an industry user of LogCaptor and want to make sure it can keep growing and being maintained, please reach out!\n\nIn any case, please star it on GitHub and share the word about it!\n\n## License\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FHakky54%2Flog-captor.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FHakky54%2Flog-captor?ref=badge_large)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHakky54%2Flog-captor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHakky54%2Flog-captor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHakky54%2Flog-captor/lists"}