├── .0pdd.yml ├── .gitattributes ├── .github └── workflows │ ├── actionlint.yml │ ├── codecov.yml │ ├── copyrights.yml │ ├── markdown-lint.yml │ ├── mvn.yml │ ├── pdd.yml │ ├── reuse.yml │ ├── typos.yml │ ├── xcop.yml │ └── yamllint.yml ├── .gitignore ├── .pdd ├── .rultor.yml ├── LICENSE.txt ├── LICENSES └── MIT.txt ├── README.md ├── REUSE.toml ├── pom.xml ├── renovate.json └── src ├── it ├── it-decoloring │ ├── invoker.properties │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jcabi │ │ │ └── log │ │ │ ├── MulticolorLayoutIntegration.java │ │ │ └── package-info.java │ │ └── test │ │ └── java │ │ └── com │ │ └── jcabi │ │ └── log │ │ ├── MulticolorLayoutIntegrationTest.java │ │ └── package-info.java └── settings.xml ├── main └── java │ └── com │ └── jcabi │ └── log │ ├── ColorfullyFormatted.java │ ├── Colors.java │ ├── ControlSequenceIndicatorFormatted.java │ ├── ConversionPattern.java │ ├── DecorException.java │ ├── DecorsManager.java │ ├── DomDecor.java │ ├── DullyFormatted.java │ ├── ExceptionDecor.java │ ├── FileDecor.java │ ├── Formatted.java │ ├── ListDecor.java │ ├── Logger.java │ ├── MsDecor.java │ ├── MulticolorLayout.java │ ├── NanoDecor.java │ ├── ObjectDecor.java │ ├── ParseableInformation.java │ ├── ParseableLevelInformation.java │ ├── PreFormatter.java │ ├── SecretDecor.java │ ├── SizeDecor.java │ ├── Supplier.java │ ├── SupplierLogger.java │ ├── TextDecor.java │ ├── TypeDecor.java │ ├── VerboseCallable.java │ ├── VerboseProcess.java │ ├── VerboseRunnable.java │ ├── VerboseThreads.java │ └── package-info.java ├── site ├── apt │ ├── decors.apt.vm │ ├── index.apt.vm │ ├── multicolor.apt.vm │ ├── threads-VerboseProcess.apt.vm │ ├── threads-VerboseRunnable.apt.vm │ └── threads-VerboseThreads.apt.vm ├── resources │ └── CNAME └── site.xml └── test ├── java └── com │ └── jcabi │ └── log │ ├── ConversionPatternTest.java │ ├── DecorMocker.java │ ├── DecorsManagerTest.java │ ├── DomDecorTest.java │ ├── ExceptionDecorTest.java │ ├── FileDecorTest.java │ ├── LineNumberTest.java │ ├── ListDecorTest.java │ ├── Logged.java │ ├── LoggerTest.java │ ├── MsDecorTest.java │ ├── MulticolorLayoutTest.java │ ├── NanoDecorTest.java │ ├── ObjectDecorTest.java │ ├── ParseableInformationTest.java │ ├── ParseableLevelInformationTest.java │ ├── PreFormatterTest.java │ ├── Printed.java │ ├── SecretDecorTest.java │ ├── SizeDecorTest.java │ ├── SupplierLoggerTest.java │ ├── TextDecorTest.java │ ├── TypeDecorTest.java │ ├── UnitTestAppender.java │ ├── VerboseCallableTest.java │ ├── VerboseProcessTest.java │ ├── VerboseRunnableTest.java │ ├── VerboseThreadsTest.java │ └── package-info.java └── resources └── log4j.properties /.0pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | errors: 5 | - yegor256@gmail.com 6 | # alerts: 7 | # github: 8 | # - yegor256 9 | 10 | tags: 11 | - pdd 12 | - bug 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Check out all text files in UNIX format, with LF as end of line 2 | # Don't change this file. If you have any ideas about it, please 3 | # submit a separate issue about it and we'll discuss. 4 | 5 | * text=auto eol=lf 6 | *.java ident 7 | *.xml ident 8 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: actionlint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | actionlint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Download actionlint 20 | id: get_actionlint 21 | run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 22 | shell: bash 23 | - name: Check workflow files 24 | run: ${{ steps.get_actionlint.outputs.executable }} -color 25 | shell: bash 26 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: codecov 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | codecov: 12 | timeout-minutes: 15 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: 11 20 | - uses: actions/cache@v4 21 | with: 22 | path: ~/.m2/repository 23 | key: maven-${{ hashFiles('**/pom.xml') }} 24 | restore-keys: | 25 | maven- 26 | - run: mvn install -Pjacoco 27 | - uses: codecov/codecov-action@v5 28 | with: 29 | token: ${{ secrets.CODECOV_TOKEN }} 30 | files: ./target/site/jacoco/jacoco.xml 31 | fail_ci_if_error: true 32 | -------------------------------------------------------------------------------- /.github/workflows/copyrights.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: copyrights 6 | 'on': 7 | push: 8 | pull_request: 9 | jobs: 10 | copyrights: 11 | timeout-minutes: 15 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: yegor256/copyrights-action@0.0.8 16 | with: 17 | globs: >- 18 | **/LICENSE.txt 19 | **/*.sh 20 | **/*.yml 21 | **/*.yaml 22 | **/*.eo 23 | **/*.xmir 24 | **/*.xml 25 | **/*.xsl 26 | **/*.xsd 27 | **/*.ini 28 | **/*.java 29 | **/*.g4 30 | **/*.properties 31 | **/*.groovy 32 | -------------------------------------------------------------------------------- /.github/workflows/markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: markdown-lint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | markdown-lint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: DavidAnson/markdownlint-cli2-action@v20.0.0 20 | -------------------------------------------------------------------------------- /.github/workflows/mvn.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: mvn 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | mvn: 15 | timeout-minutes: 15 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-24.04, windows-2022, macos-15] 20 | java: [11, 17] 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-java@v4 24 | with: 25 | distribution: 'temurin' 26 | java-version: ${{ matrix.java }} 27 | - uses: actions/cache@v4 28 | with: 29 | path: ~/.m2/repository 30 | key: ${{ runner.os }}-jdk-${{ matrix.java }}-maven-${{ hashFiles('**/pom.xml') }} 31 | restore-keys: | 32 | ${{ runner.os }}-jdk-${{ matrix.java }}-maven- 33 | - run: java -version 34 | - run: mvn -version 35 | - run: mvn --errors --batch-mode clean install -Pqulice 36 | -------------------------------------------------------------------------------- /.github/workflows/pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: pdd 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | pdd: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: volodya-lombrozo/pdd-action@master 20 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: reuse 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | reuse: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: fsfe/reuse-action@v5 20 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: typos 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | typos: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: crate-ci/typos@v1.32.0 20 | -------------------------------------------------------------------------------- /.github/workflows/xcop.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: xcop 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | xcop: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: g4s8/xcop-action@master 20 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: yamllint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | yamllint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ibiqlik/action-yamllint@v3 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .idea/ 4 | .settings 5 | /.classpath 6 | /.project 7 | jcabi-log.iml 8 | node_modules/ 9 | target/ 10 | -------------------------------------------------------------------------------- /.pdd: -------------------------------------------------------------------------------- 1 | --source=. 2 | --verbose 3 | --exclude target/**/* 4 | --exclude src/main/resources/images/**/* 5 | --rule min-words:20 6 | --rule min-estimate:0 7 | --rule max-estimate:90 8 | -------------------------------------------------------------------------------- /.rultor.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | docker: 6 | image: yegor256/java 7 | assets: 8 | settings.xml: yegor256/home#assets/jcabi/settings.xml 9 | secring.gpg: yegor256/home#assets/secring.gpg 10 | pubring.gpg: yegor256/home#assets/pubring.gpg 11 | install: | 12 | pdd --file=/dev/null 13 | merge: 14 | script: | 15 | mvn clean install -Pqulice --errors --batch-mode 16 | release: 17 | pre: false 18 | script: |- 19 | [[ "${tag}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit -1 20 | mvn versions:set "-DnewVersion=${tag}" --batch-mode 21 | git commit -am "${tag}" 22 | export MAVEN_OPTS="--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED" 23 | mvn clean deploy -Pqulice,sonatype,jcabi -B -e -s ../settings.xml 24 | mvn clean site-deploy -Psite -B -e -s ../settings.xml 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2025 Yegor Bugayenko 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 1) Redistributions of source code must retain the above 7 | copyright notice, this list of conditions and the following 8 | disclaimer. 2) Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 3) Neither the name of the jcabi.com nor 12 | the names of its contributors may be used to endorse or promote 13 | products derived from this software without specific prior written 14 | permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 18 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 25 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 27 | OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2025 Yegor Bugayenko 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 1) Redistributions of source code must retain the above 7 | copyright notice, this list of conditions and the following 8 | disclaimer. 2) Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 3) Neither the name of the jcabi.com nor 12 | the names of its contributors may be used to endorse or promote 13 | products derived from this software without specific prior written 14 | permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 18 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 25 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 27 | OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | [![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org) 4 | [![DevOps By Rultor.com](https://www.rultor.com/b/jcabi/jcabi-log)](https://www.rultor.com/p/jcabi/jcabi-log) 5 | [![We recommend IntelliJ IDEA](https://www.elegantobjects.org/intellij-idea.svg)](https://www.jetbrains.com/idea/) 6 | 7 | [![mvn](https://github.com/jcabi/jcabi-log/actions/workflows/mvn.yml/badge.svg)](https://github.com/jcabi/jcabi-log/actions/workflows/mvn.yml) 8 | [![PDD status](https://www.0pdd.com/svg?name=jcabi/jcabi-log)](https://www.0pdd.com/p?name=jcabi/jcabi-log) 9 | [![codecov](https://codecov.io/gh/jcabi/jcabi-log/branch/master/graph/badge.svg)](https://codecov.io/gh/jcabi/jcabi-log) 10 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jcabi/jcabi-log/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jcabi/jcabi-log) 11 | [![Javadoc](https://javadoc.io/badge/com.jcabi/jcabi-log.svg)](https://www.javadoc.io/doc/com.jcabi/jcabi-log) 12 | [![Hits-of-Code](https://hitsofcode.com/github/jcabi/jcabi-log)](https://hitsofcode.com/view/github/jcabi/jcabi-log) 13 | 14 | More details are here: [log.jcabi.com](https://log.jcabi.com/index.html) 15 | 16 | Read this blog post: 17 | [_Get Rid of Java Static Loggers_](https://www.yegor256.com/2014/05/23/avoid-java-static-logger.html) 18 | 19 | `Logger` is a convenient static wrapper of 20 | [slf4j](http://www.slf4j.org/) 21 | (don't forget to include one of 22 | [SLF4J Bindings](http://www.slf4j.org/manual.html#binding) 23 | into the project): 24 | 25 | ```java 26 | import com.jcabi.log.Logger; 27 | class Foo { 28 | void bar(int value) { 29 | Logger.debug(this, "method #bar(%d) was called", value); 30 | } 31 | } 32 | ``` 33 | 34 | Besides standard `%s` placeholders inside the format string, you can use 35 | other custom ones, which help formatting common values faster: 36 | 37 | * `%[file]s` --- absolute file name ➜ file name relative to current directory 38 | * `%[text]s` --- any string ➜ pretty looking text, short enough, and escaped 39 | * `%[exception]s` --- `Exception` ➜ stacktrace 40 | * `%[list]s` --- `Iterable` ➜ pretty formatted list, in one line 41 | * `%[size]s` --- size in bytes ➜ Kb, Mb, Gb, Tb, and so on 42 | * `%[ms]s` --- milliseconds ➜ ms, sec, min, hours, etc. 43 | * `%[nano]s` --- nanoseconds ➜ µs, ms, sec, min, hours, etc. 44 | * `%[type]s` --- `Class` ➜ name of it 45 | * `%[secret]s` --- any string ➜ stars 46 | * `%[dom]s` --- `org.w3c.domDocument` ➜ pretty printed/formatted XML 47 | 48 | You are welcome to 49 | [suggest](https://github.com/jcabi/jcabi-log/blob/master/src/main/java/com/jcabi/log/DecorsManager.java) 50 | your own "decors". 51 | 52 | ## How to contribute? 53 | 54 | Fork the repository, make changes, submit a pull request. 55 | We promise to review your changes same day and apply to 56 | the `master` branch, if they look correct. 57 | 58 | Please run Maven build before submitting a pull request: 59 | 60 | ```bash 61 | mvn clean install -Pqulice 62 | ``` 63 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | version = 1 5 | [[annotations]] 6 | path = [ 7 | ".DS_Store", 8 | ".gitattributes", 9 | ".gitignore", 10 | ".pdd", 11 | "**.json", 12 | "**.md", 13 | "**.txt", 14 | "**.vm", 15 | "**/.DS_Store", 16 | "**/.gitignore", 17 | "**/.pdd", 18 | "**/*.csv", 19 | "**/*.jpg", 20 | "**/*.json", 21 | "**/*.md", 22 | "**/*.pdf", 23 | "**/*.png", 24 | "**/*.svg", 25 | "**/*.txt", 26 | "**/*.vm", 27 | "**/CNAME", 28 | "CNAME", 29 | "README.md", 30 | "renovate.json", 31 | ] 32 | precedence = "override" 33 | SPDX-FileCopyrightText = "Copyright (c) 2025 Yegor Bugayenko" 34 | SPDX-License-Identifier = "MIT" 35 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 4.0.0 33 | 34 | com.jcabi 35 | jcabi 36 | 1.37.1 37 | 38 | jcabi-log 39 | 1.0-SNAPSHOT 40 | jcabi-log 41 | bundle 42 | Wrapper of SLF4J and a few supplementary logging classes 43 | 44 | github 45 | https://github.com/jcabi/jcabi-log/issues 46 | 47 | 48 | travis 49 | https://travis-ci.org/jcabi/jcabi-log 50 | 51 | 52 | scm:git:github.com:jcabi/jcabi-log.git 53 | scm:git:github.com:jcabi/jcabi-log.git 54 | https://github.com/jcabi/jcabi-log 55 | 56 | 57 | 58 | github-pages 59 | https://log.jcabi.com/ 60 | 61 | 62 | 63 | 64 | 65 | log4j 66 | log4j 67 | 1.2.17 68 | compile 69 | true 70 | 71 | 72 | 77 | org.slf4j 78 | slf4j-api 79 | 2.0.16 80 | 81 | 82 | org.slf4j 83 | slf4j-reload4j 84 | 2.0.16 85 | test 86 | 87 | 88 | org.junit.jupiter 89 | junit-jupiter-api 90 | 5.9.0 91 | test 92 | 93 | 94 | org.junit.jupiter 95 | junit-jupiter-params 96 | 5.9.0 97 | test 98 | 99 | 100 | org.mockito 101 | mockito-core 102 | 5.14.2 103 | test 104 | 105 | 106 | org.apache.commons 107 | commons-lang3 108 | 3.17.0 109 | test 110 | 111 | 112 | org.apache.commons 113 | commons-text 114 | 1.12.0 115 | test 116 | 117 | 118 | 119 | 120 | 121 | maven-surefire-plugin 122 | 123 | 124 | Cp1251 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | qulice 133 | 134 | 135 | 136 | com.qulice 137 | qulice-maven-plugin 138 | 0.22.0 139 | 140 | 141 | findbugs:.* 142 | duplicatefinder:.* 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/it/it-decoloring/invoker.properties: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | invoker.goals = clean test 4 | -------------------------------------------------------------------------------- /src/it/it-decoloring/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 4.0.0 33 | 34 | com.jcabi 35 | jcabi 36 | 1.37.1 37 | 38 | com.jcabi 39 | jcabi-log-test 40 | 1.0-SNAPSHOT 41 | 42 | 43 | @project.groupId@ 44 | @project.artifactId@ 45 | @project.version@ 46 | 47 | 48 | org.apache.commons 49 | commons-text 50 | test 51 | 52 | 53 | log4j 54 | log4j 55 | test 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/it/it-decoloring/src/main/java/com/jcabi/log/MulticolorLayoutIntegration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Integration tests for MulticolorLayout. These have to 9 | * be run with maven-invoker-plugin because they set the system property 10 | * com.jcabi.log.coloring, which interferes with other tests (tests are run 11 | * by multiple threads at once) 12 | * @since 0.1 13 | */ 14 | public final class MulticolorLayoutIntegration { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/it/it-decoloring/src/main/java/com/jcabi/log/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | /** 7 | * Integration tests. 8 | */ 9 | package com.jcabi.log; 10 | -------------------------------------------------------------------------------- /src/it/it-decoloring/src/test/java/com/jcabi/log/MulticolorLayoutIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.apache.commons.text.StringEscapeUtils; 8 | import org.apache.log4j.Level; 9 | import org.apache.log4j.spi.LoggingEvent; 10 | import org.hamcrest.MatcherAssert; 11 | import org.hamcrest.Matchers; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.Mockito; 14 | 15 | /** 16 | * Integration tests for MulticolorLayout. These have to 17 | * be run with maven-invoker-plugin because they set the system property 18 | * com.jcabi.log.coloring, which interferes with other tests (tests are run 19 | * by multiple threads at once) 20 | * @since 0.1 21 | */ 22 | final class MulticolorLayoutIntegrationTest { 23 | /** 24 | * Conversation pattern for test case. 25 | */ 26 | private static final String CONV_PATTERN = "[%color{%p}] %color{%m}"; 27 | 28 | /** 29 | * Property that dictates wheter the text should be coloured or not. 30 | */ 31 | private static final String COLORING_PROPERTY = "com.jcabi.log.coloring"; 32 | 33 | /** 34 | * MulticolorLayout disables default color 35 | * when -Dcom.jcabi.log.coloring=false. 36 | */ 37 | @Test 38 | void disablesDefaultColor() { 39 | final MulticolorLayout layout = new MulticolorLayout(); 40 | layout.setConversionPattern( 41 | MulticolorLayoutIntegrationTest.CONV_PATTERN 42 | ); 43 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 44 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 45 | Mockito.doReturn("no color").when(event).getRenderedMessage(); 46 | MatcherAssert.assertThat( 47 | StringEscapeUtils.escapeJava(layout.format(event)), 48 | Matchers.equalTo( 49 | "[\\u001B[2;37mDEBUG\\u001B[m] \\u001B[2;37mno color\\u001B[m" 50 | ) 51 | ); 52 | System.getProperties().setProperty( 53 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 54 | Boolean.FALSE.toString() 55 | ); 56 | try { 57 | MatcherAssert.assertThat( 58 | StringEscapeUtils.escapeJava(layout.format(event)), 59 | Matchers.equalTo( 60 | "[DEBUG] no color" 61 | ) 62 | ); 63 | } finally { 64 | System.getProperties().setProperty( 65 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 66 | Boolean.TRUE.toString() 67 | ); 68 | } 69 | } 70 | 71 | /** 72 | * MulticolorLayout disables the color that overwrites the default one. 73 | */ 74 | @Test 75 | void disablesOverridenDefaultColor() { 76 | final MulticolorLayout layout = new MulticolorLayout(); 77 | layout.setConversionPattern("[%color{%p}] %m"); 78 | layout.setLevels("DEBUG:2;10"); 79 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 80 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 81 | Mockito.doReturn("no colour text").when(event).getRenderedMessage(); 82 | MatcherAssert.assertThat( 83 | StringEscapeUtils.escapeJava(layout.format(event)), 84 | Matchers.equalTo( 85 | "[\\u001B[2;10mDEBUG\\u001B[m] no colour text" 86 | ) 87 | ); 88 | System.getProperties().setProperty( 89 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 90 | Boolean.FALSE.toString() 91 | ); 92 | try { 93 | MatcherAssert.assertThat( 94 | StringEscapeUtils.escapeJava(layout.format(event)), 95 | Matchers.equalTo( 96 | "[DEBUG] no colour text" 97 | ) 98 | ); 99 | } finally { 100 | System.getProperties().setProperty( 101 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 102 | Boolean.TRUE.toString() 103 | ); 104 | } 105 | } 106 | 107 | /** 108 | * MulticolorLayout disables constant color 109 | * when -Dcom.jcabi.log.coloring=false. 110 | */ 111 | @Test 112 | void disablesConstantColor() { 113 | final MulticolorLayout layout = new MulticolorLayout(); 114 | layout.setConversionPattern("[%color-blue{%p}] %color-blue{%m}"); 115 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 116 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 117 | Mockito.doReturn("no color text").when(event).getRenderedMessage(); 118 | MatcherAssert.assertThat( 119 | StringEscapeUtils.escapeJava(layout.format(event)), 120 | Matchers.equalTo( 121 | "[\\u001B[34mDEBUG\\u001B[m] \\u001B[34mno color text\\u001B[m" 122 | ) 123 | ); 124 | System.getProperties().setProperty( 125 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 126 | Boolean.FALSE.toString() 127 | ); 128 | try { 129 | MatcherAssert.assertThat( 130 | StringEscapeUtils.escapeJava(layout.format(event)), 131 | Matchers.equalTo( 132 | "[DEBUG] no color text" 133 | ) 134 | ); 135 | } finally { 136 | System.getProperties().setProperty( 137 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 138 | Boolean.TRUE.toString() 139 | ); 140 | } 141 | } 142 | 143 | /** 144 | * MulticolorLayout disables the color that overwrites the constant one. 145 | */ 146 | @Test 147 | void disablesOverridenConstantColor() { 148 | final MulticolorLayout layout = new MulticolorLayout(); 149 | layout.setConversionPattern("[%color-red{%p}] %color-red{%m}"); 150 | layout.setColors("red:12"); 151 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 152 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 153 | Mockito.doReturn("test").when(event).getRenderedMessage(); 154 | MatcherAssert.assertThat( 155 | StringEscapeUtils.escapeJava(layout.format(event)), 156 | Matchers.equalTo( 157 | "[\\u001B[12mDEBUG\\u001B[m] \\u001B[12mtest\\u001B[m" 158 | ) 159 | ); 160 | System.getProperties().setProperty( 161 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 162 | Boolean.FALSE.toString() 163 | ); 164 | try { 165 | MatcherAssert.assertThat( 166 | StringEscapeUtils.escapeJava(layout.format(event)), 167 | Matchers.equalTo( 168 | "[DEBUG] test" 169 | ) 170 | ); 171 | } finally { 172 | System.getProperties().setProperty( 173 | MulticolorLayoutIntegrationTest.COLORING_PROPERTY, 174 | Boolean.TRUE.toString() 175 | ); 176 | } 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/it/it-decoloring/src/test/java/com/jcabi/log/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | /** 7 | * Integration tests. 8 | */ 9 | package com.jcabi.log; 10 | -------------------------------------------------------------------------------- /src/it/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 33 | 34 | it-repo 35 | 36 | true 37 | 38 | 39 | 40 | local.central 41 | @localRepositoryUrl@ 42 | 43 | true 44 | 45 | 46 | true 47 | 48 | 49 | 50 | 51 | 52 | local.central 53 | @localRepositoryUrl@ 54 | 55 | true 56 | 57 | 58 | true 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ColorfullyFormatted.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Formats a log event using ANSI color codes. 9 | * @since 0.18 10 | */ 11 | class ColorfullyFormatted implements Formatted { 12 | 13 | /** 14 | * The basic information to be formatted with colors. 15 | */ 16 | private final transient String basic; 17 | 18 | /** 19 | * Color used as the replacement. 20 | */ 21 | private final transient String color; 22 | 23 | /** 24 | * Constructor. 25 | * @param bas Basic string to be formatted 26 | * @param col Color to be used to paint the output 27 | */ 28 | ColorfullyFormatted(final String bas, final String col) { 29 | this.basic = bas; 30 | this.color = col; 31 | } 32 | 33 | @Override 34 | public String format() { 35 | return this.basic.replaceAll( 36 | new ControlSequenceIndicatorFormatted("%s\\?m").format(), 37 | String.format( 38 | "%s%sm", 39 | new ControlSequenceIndicatorFormatted("%s").format(), 40 | this.color 41 | ) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/Colors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.ConcurrentMap; 9 | 10 | /** 11 | * Store human readable color data. 12 | * @since 0.18 13 | */ 14 | public class Colors { 15 | 16 | /** 17 | * Colors with names. 18 | */ 19 | private final transient ConcurrentMap map; 20 | 21 | /** 22 | * Public ctor. 23 | */ 24 | public Colors() { 25 | this.map = Colors.colorMap(); 26 | } 27 | 28 | /** 29 | * Add color to color map. 30 | * @param key Key to add 31 | * @param value Value to add 32 | */ 33 | public final void addColor(final String key, final String value) { 34 | this.map.put(key, value); 35 | } 36 | 37 | /** 38 | * Convert our text to ANSI color. 39 | * @param meta Meta text 40 | * @return ANSI color 41 | */ 42 | public final String ansi(final String meta) { 43 | final String ansi; 44 | if (meta == null) { 45 | ansi = "?"; 46 | } else if (meta.matches("[a-z]+")) { 47 | ansi = this.map.get(meta); 48 | if (ansi == null) { 49 | throw new IllegalArgumentException( 50 | String.format("unknown color '%s'", meta) 51 | ); 52 | } 53 | } else { 54 | ansi = meta; 55 | } 56 | return ansi; 57 | } 58 | 59 | /** 60 | * Color map. 61 | * @return Map of colors 62 | */ 63 | private static ConcurrentMap colorMap() { 64 | final ConcurrentMap map = 65 | new ConcurrentHashMap<>(0); 66 | map.put("black", "30"); 67 | map.put("blue", "34"); 68 | map.put("cyan", "36"); 69 | map.put("green", "32"); 70 | map.put("magenta", "35"); 71 | map.put("red", "31"); 72 | map.put("yellow", "33"); 73 | map.put("white", "37"); 74 | return map; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ControlSequenceIndicatorFormatted.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Replaces string format with a Control Sequence Indicator. 9 | * @since 0.18 10 | */ 11 | public class ControlSequenceIndicatorFormatted implements Formatted { 12 | 13 | /** 14 | * Pattern to be used to find replacement points. 15 | */ 16 | private final transient String pattern; 17 | 18 | /** 19 | * Ctor. 20 | * @param pat Pattern to be used to find replacement points 21 | */ 22 | public ControlSequenceIndicatorFormatted(final String pat) { 23 | this.pattern = pat; 24 | } 25 | 26 | @Override 27 | public final String format() { 28 | return String.format(this.pattern, "\u001b\\["); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ConversionPattern.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Generates the conversion pattern. 12 | * @since 0.18 13 | */ 14 | class ConversionPattern { 15 | 16 | /** 17 | * Regular expression for all matches. 18 | */ 19 | private static final Pattern METAS = Pattern.compile( 20 | "%color(?:-([a-z]+|[0-9]{1,3};[0-9]{1,3};[0-9]{1,3}))?\\{" 21 | ); 22 | 23 | /** 24 | * Pattern to be validated. 25 | */ 26 | private final transient String pattern; 27 | 28 | /** 29 | * Colors to be used. 30 | */ 31 | private final transient Colors colors; 32 | 33 | /** 34 | * Constructor. 35 | * @param pat Pattern to be used 36 | * @param col Colors to be used 37 | */ 38 | ConversionPattern(final String pat, final Colors col) { 39 | this.pattern = pat; 40 | this.colors = col; 41 | } 42 | 43 | /** 44 | * Generates the conversion pattern. 45 | * @return Conversion pattern 46 | */ 47 | public String generate() { 48 | String remaining = this.pattern; 49 | final Matcher matcher = ConversionPattern.METAS.matcher( 50 | remaining 51 | ); 52 | final StringBuffer buf = new StringBuffer(0); 53 | while (matcher.find()) { 54 | final int argstart = matcher.end(); 55 | final int argend = findArgumentEnd(remaining, argstart); 56 | if (argend < 0) { 57 | break; 58 | } 59 | matcher.appendReplacement(buf, ""); 60 | buf.append(ConversionPattern.csi()) 61 | .append(this.colors.ansi(matcher.group(1))) 62 | .append('m') 63 | .append(remaining, argstart, argend) 64 | .append(ConversionPattern.csi()) 65 | .append('m'); 66 | remaining = remaining.substring(argend + 1); 67 | matcher.reset(remaining); 68 | } 69 | matcher.appendTail(buf); 70 | return buf.toString(); 71 | } 72 | 73 | /** 74 | * Find the matching closing curly brace while keeping any nested curly 75 | * brace pairs balanced. 76 | * @param pattern Pattern to find the match in. 77 | * @param start Index of first character after the opening curly brace 78 | * @return Index of the closing curly brace, or -1 if not found 79 | */ 80 | private static int findArgumentEnd(final String pattern, final int start) { 81 | int count = 1; 82 | int index = start; 83 | while (index < pattern.length()) { 84 | final char character = pattern.charAt(index); 85 | if (character == '}') { 86 | --count; 87 | if (count == 0) { 88 | break; 89 | } 90 | } else if (character == '{') { 91 | ++count; 92 | } 93 | ++index; 94 | } 95 | if (index == pattern.length()) { 96 | index = -1; 97 | } 98 | return index; 99 | } 100 | 101 | /** 102 | * Formats a string with a Control Sequence Information. 103 | * @return Formatted string 104 | */ 105 | private static String csi() { 106 | return new ControlSequenceIndicatorFormatted("%s").format(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/DecorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Exception if some problem with decor. 9 | * 10 | * @since 0.1 11 | */ 12 | final class DecorException extends Exception { 13 | 14 | /** 15 | * Serialization marker. 16 | */ 17 | private static final long serialVersionUID = 0x7526FA78EEDAC465L; 18 | 19 | /** 20 | * Public ctor. 21 | * @param cause Cause of it 22 | * @param format The message 23 | * @param args Optional arguments 24 | */ 25 | DecorException(final Throwable cause, final String format, 26 | final Object... args) { 27 | super(String.format(format, args), cause); 28 | } 29 | 30 | /** 31 | * Public ctor. 32 | * @param format The message 33 | * @param args Optional arguments 34 | */ 35 | DecorException(final String format, final Object... args) { 36 | super(String.format(format, args)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/DecorsManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.Formattable; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ConcurrentMap; 12 | 13 | /** 14 | * Manager of all decors. 15 | * 16 | * @since 0.1 17 | */ 18 | final class DecorsManager { 19 | 20 | /** 21 | * Storage of all found decors. 22 | */ 23 | private static final ConcurrentMap> DECORS = 24 | new ConcurrentHashMap<>(0); 25 | 26 | static { 27 | DecorsManager.DECORS.put("file", FileDecor.class); 28 | DecorsManager.DECORS.put("dom", DomDecor.class); 29 | DecorsManager.DECORS.put("exception", ExceptionDecor.class); 30 | DecorsManager.DECORS.put("list", ListDecor.class); 31 | DecorsManager.DECORS.put("ms", MsDecor.class); 32 | DecorsManager.DECORS.put("nano", NanoDecor.class); 33 | DecorsManager.DECORS.put("object", ObjectDecor.class); 34 | DecorsManager.DECORS.put("size", SizeDecor.class); 35 | DecorsManager.DECORS.put("secret", SecretDecor.class); 36 | DecorsManager.DECORS.put("text", TextDecor.class); 37 | DecorsManager.DECORS.put("type", TypeDecor.class); 38 | } 39 | 40 | /** 41 | * Private ctor. 42 | */ 43 | private DecorsManager() { 44 | // empty 45 | } 46 | 47 | /** 48 | * Get decor by key. 49 | * @param key Key for the formatter to be used to fmt the arguments 50 | * @param arg The arbument to supply 51 | * @return The decor 52 | * @throws DecorException If some problem 53 | */ 54 | @SuppressWarnings("PMD.ProhibitPublicStaticMethods") 55 | public static Formattable decor(final String key, final Object arg) 56 | throws DecorException { 57 | final Class type = DecorsManager.find(key); 58 | final Formattable decor; 59 | try { 60 | decor = (Formattable) DecorsManager.ctor(type).newInstance(arg); 61 | } catch (final InstantiationException ex) { 62 | throw new DecorException( 63 | ex, 64 | "Can't instantiate %s(%s)", 65 | type.getName(), 66 | arg.getClass().getName() 67 | ); 68 | } catch (final IllegalAccessException ex) { 69 | throw new DecorException( 70 | ex, 71 | "Can't access %s(%s)", 72 | type.getName(), 73 | arg.getClass().getName() 74 | ); 75 | } catch (final InvocationTargetException ex) { 76 | throw new DecorException( 77 | ex, 78 | "Can't invoke %s(%s)", 79 | type.getName(), 80 | arg.getClass().getName() 81 | ); 82 | } 83 | return decor; 84 | } 85 | 86 | /** 87 | * Find decor. 88 | * @param key Key for the formatter to be used to fmt the arguments 89 | * @return The type of decor found 90 | * @throws DecorException If some problem 91 | */ 92 | @SuppressWarnings("unchecked") 93 | private static Class find(final String key) 94 | throws DecorException { 95 | final Class type; 96 | if (DecorsManager.DECORS.containsKey(key)) { 97 | type = DecorsManager.DECORS.get(key); 98 | } else { 99 | try { 100 | type = (Class) Class.forName(key); 101 | } catch (final ClassNotFoundException ex) { 102 | throw new DecorException( 103 | ex, 104 | "Decor '%s' not found and class can't be instantiated", 105 | key 106 | ); 107 | } 108 | } 109 | return type; 110 | } 111 | 112 | /** 113 | * Get ctor of the type. 114 | * @param type The type 115 | * @return The ctor 116 | * @throws DecorException If some problem 117 | */ 118 | private static Constructor ctor(final Class type) 119 | throws DecorException { 120 | final Constructor[] ctors = type.getDeclaredConstructors(); 121 | if (ctors.length != 1) { 122 | throw new DecorException( 123 | "%s should have just one one-arg ctor, but there are %d", 124 | type.getName(), 125 | ctors.length 126 | ); 127 | } 128 | final Constructor ctor = ctors[0]; 129 | if (ctor.getParameterTypes().length != 1) { 130 | throw new DecorException( 131 | "%s public ctor should have just once parameter", 132 | type.getName() 133 | ); 134 | } 135 | return ctor; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/DomDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.StringWriter; 8 | import java.util.Formattable; 9 | import java.util.Formatter; 10 | import javax.xml.transform.OutputKeys; 11 | import javax.xml.transform.Transformer; 12 | import javax.xml.transform.TransformerConfigurationException; 13 | import javax.xml.transform.TransformerException; 14 | import javax.xml.transform.TransformerFactory; 15 | import javax.xml.transform.dom.DOMSource; 16 | import javax.xml.transform.stream.StreamResult; 17 | import org.w3c.dom.Node; 18 | 19 | /** 20 | * Decorates XML Document. 21 | * 22 | * @since 0.1 23 | */ 24 | final class DomDecor implements Formattable { 25 | 26 | /** 27 | * DOM transformer factory, DOM. 28 | */ 29 | private static final TransformerFactory FACTORY = 30 | TransformerFactory.newInstance(); 31 | 32 | /** 33 | * The document. 34 | */ 35 | private final transient Node node; 36 | 37 | /** 38 | * Public ctor. 39 | * @param doc The document 40 | * @throws DecorException If some problem with it 41 | */ 42 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") 43 | DomDecor(final Object doc) throws DecorException { 44 | if (doc != null && !(doc instanceof Node)) { 45 | throw new DecorException( 46 | String.format( 47 | "Instance of org.w3c.dom.Node required, while %s provided", 48 | doc.getClass().getName() 49 | ) 50 | ); 51 | } 52 | this.node = (Node) doc; 53 | } 54 | 55 | // @checkstyle ParameterNumber (4 lines) 56 | @Override 57 | public void formatTo(final Formatter formatter, final int flags, 58 | final int width, final int precision) { 59 | final StringWriter writer = new StringWriter(); 60 | if (this.node == null) { 61 | writer.write("NULL"); 62 | } else { 63 | try { 64 | final Transformer trans = DomDecor.FACTORY.newTransformer(); 65 | trans.setOutputProperty(OutputKeys.INDENT, "yes"); 66 | trans.setOutputProperty(OutputKeys.STANDALONE, "no"); 67 | trans.transform( 68 | new DOMSource(this.node), 69 | new StreamResult(writer) 70 | ); 71 | } catch (final TransformerException ex) { 72 | throw new IllegalStateException(ex); 73 | } 74 | } 75 | formatter.format("%s", writer); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/DullyFormatted.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Formats a log event without using ANSI color codes. 9 | * @since 0.18 10 | */ 11 | class DullyFormatted implements Formatted { 12 | 13 | /** 14 | * String to be formatted. 15 | */ 16 | private final transient String basic; 17 | 18 | /** 19 | * Contructor. 20 | * @param bas String to be formatted 21 | */ 22 | DullyFormatted(final String bas) { 23 | this.basic = bas; 24 | } 25 | 26 | @Override 27 | public String format() { 28 | return this.basic.replaceAll( 29 | new ControlSequenceIndicatorFormatted("%s([0-9]*|\\?)m").format(), 30 | "" 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ExceptionDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.PrintWriter; 8 | import java.io.StringWriter; 9 | import java.util.Formattable; 10 | import java.util.FormattableFlags; 11 | import java.util.Formatter; 12 | 13 | /** 14 | * Decorates an exception. 15 | * 16 | *

For example: 17 | * 18 | *

19 |  * try {
20 |  *   // ...
21 |  * } catch (final IOException ex) {
22 |  *   Logger.error("failed to open file: %[exception]s", ex);
23 |  *   throw new IllegalArgumentException(ex);
24 |  * }
25 |  * 
26 | * 27 | * @since 0.1 28 | */ 29 | final class ExceptionDecor implements Formattable { 30 | 31 | /** 32 | * The exception. 33 | */ 34 | private final transient Throwable throwable; 35 | 36 | /** 37 | * Public ctor. 38 | * @param thr The exception 39 | */ 40 | ExceptionDecor(final Throwable thr) { 41 | this.throwable = thr; 42 | } 43 | 44 | // @checkstyle ParameterNumber (4 lines) 45 | @Override 46 | public void formatTo(final Formatter formatter, final int flags, 47 | final int width, final int precision) { 48 | final String text; 49 | if (this.throwable == null) { 50 | text = "NULL"; 51 | } else if ((flags & FormattableFlags.ALTERNATE) == 0) { 52 | final StringWriter writer = new StringWriter(); 53 | this.throwable.printStackTrace(new PrintWriter(writer)); 54 | text = writer.toString(); 55 | } else { 56 | text = this.throwable.getMessage(); 57 | } 58 | formatter.format("%s", text); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/FileDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.StringWriter; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Formattable; 11 | import java.util.Formatter; 12 | 13 | /** 14 | * Decorates File. 15 | * 16 | * @since 0.1 17 | */ 18 | final class FileDecor implements Formattable { 19 | 20 | /** 21 | * The path. 22 | */ 23 | private final transient Object path; 24 | 25 | /** 26 | * Public ctor. 27 | * @param file The file 28 | */ 29 | FileDecor(final Object file) { 30 | this.path = file; 31 | } 32 | 33 | // @checkstyle ParameterNumber (4 lines) 34 | @Override 35 | public void formatTo(final Formatter formatter, final int flags, 36 | final int width, final int precision) { 37 | final StringWriter writer = new StringWriter(); 38 | if (this.path == null) { 39 | writer.write("NULL"); 40 | } else { 41 | final Path self = Paths.get(this.path.toString()).toAbsolutePath(); 42 | final Path root = Paths.get("").toAbsolutePath(); 43 | Path rlt; 44 | try { 45 | rlt = root.relativize(self); 46 | } catch (final IllegalArgumentException ex) { 47 | rlt = self; 48 | } 49 | String rel = rlt.toString(); 50 | if (rel.startsWith("..")) { 51 | rel = self.toString(); 52 | } 53 | if (rel.isEmpty()) { 54 | rel = "./"; 55 | } 56 | writer.write(rel); 57 | } 58 | formatter.format("%s", writer); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/Formatted.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Contract for a class that know how to format something. 9 | * @since 0.18 10 | */ 11 | interface Formatted { 12 | 13 | /** 14 | * Return something formatted. 15 | * @return Formatted version of something 16 | */ 17 | String format(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ListDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.Formattable; 10 | import java.util.Formatter; 11 | 12 | /** 13 | * Format list. 14 | * @since 0.1 15 | */ 16 | final class ListDecor implements Formattable { 17 | 18 | /** 19 | * The list. 20 | */ 21 | private final transient Collection list; 22 | 23 | /** 24 | * Public ctor. 25 | * @param obj The object to format 26 | * @throws DecorException If some problem with it 27 | */ 28 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") 29 | ListDecor(final Object obj) throws DecorException { 30 | if (obj == null || obj instanceof Collection) { 31 | this.list = Collection.class.cast(obj); 32 | } else if (obj instanceof Object[]) { 33 | this.list = Arrays.asList((Object[]) obj); 34 | } else { 35 | throw new DecorException( 36 | String.format( 37 | "Collection or array required, while %s provided", 38 | obj.getClass().getName() 39 | ) 40 | ); 41 | } 42 | } 43 | 44 | // @checkstyle ParameterNumber (4 lines) 45 | @Override 46 | public void formatTo(final Formatter formatter, final int flags, 47 | final int width, final int precision) { 48 | final StringBuilder builder = new StringBuilder(0); 49 | builder.append('['); 50 | if (this.list == null) { 51 | builder.append("NULL"); 52 | } else { 53 | boolean first = true; 54 | for (final Object item : this.list) { 55 | if (!first) { 56 | builder.append(", "); 57 | } 58 | builder.append(String.format("\"%s\"", item)); 59 | first = false; 60 | } 61 | } 62 | builder.append(']'); 63 | formatter.format("%s", builder); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/MsDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.FormattableFlags; 9 | import java.util.Formatter; 10 | 11 | /** 12 | * Decorate time interval in milliseconds. 13 | * 14 | *

For example: 15 | * 16 | *

 17 |  * final long start = System.currentTimeMillis();
 18 |  * // some operations
 19 |  * Logger.debug("completed in %[ms]s", System.currentTimeMillis() - start);
 20 |  * 
21 | * 22 | * @since 0.1 23 | */ 24 | final class MsDecor implements Formattable { 25 | 26 | /** 27 | * The period to work with, in milliseconds. 28 | */ 29 | private final transient Double millis; 30 | 31 | /** 32 | * Public ctor. 33 | * @param msec The interval in milliseconds 34 | */ 35 | @SuppressWarnings( 36 | { 37 | "PMD.NullAssignment", 38 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors" 39 | } 40 | ) 41 | MsDecor(final Long msec) { 42 | if (msec == null) { 43 | this.millis = null; 44 | } else { 45 | this.millis = Double.valueOf(msec); 46 | } 47 | } 48 | 49 | // @checkstyle ParameterNumber (4 lines) 50 | @Override 51 | public void formatTo(final Formatter formatter, final int flags, 52 | final int width, final int precision) { 53 | if (this.millis == null) { 54 | formatter.format("NULL"); 55 | } else { 56 | final StringBuilder format = new StringBuilder(0); 57 | format.append('%'); 58 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags 59 | .LEFT_JUSTIFY) { 60 | format.append('-'); 61 | } 62 | if (width > 0) { 63 | format.append(Integer.toString(width)); 64 | } 65 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags 66 | .UPPERCASE) { 67 | format.append('S'); 68 | } else { 69 | format.append('s'); 70 | } 71 | formatter.format(format.toString(), this.toText(precision)); 72 | } 73 | } 74 | 75 | /** 76 | * Create text. 77 | * @param precision The precision 78 | * @return The text 79 | */ 80 | private String toText(final int precision) { 81 | final double number; 82 | final String title; 83 | if (this.millis < 1000.0) { 84 | number = this.millis; 85 | title = "ms"; 86 | } else if (this.millis < (double) (1000L * 60L)) { 87 | number = this.millis / 1000.0; 88 | title = "s"; 89 | } else if (this.millis < (double) (1000L * 60L * 60L)) { 90 | number = this.millis / (double) (1000L * 60L); 91 | title = "min"; 92 | } else if (this.millis < (double) (1000L * 60L * 60L * 24L)) { 93 | number = this.millis / (double) (1000L * 60L * 60L); 94 | title = "hr"; 95 | } else if (this.millis < (double) (1000L * 60L * 60L * 24L * 30L)) { 96 | number = this.millis / (double) (1000L * 60L * 60L * 24L); 97 | title = "days"; 98 | } else { 99 | number = this.millis / (double) (1000L * 60L * 60L * 24L * 30L); 100 | title = "mon"; 101 | } 102 | final String format; 103 | if (precision >= 0) { 104 | format = String.format("%%.%df%%s", precision); 105 | } else { 106 | format = "%.0f%s"; 107 | } 108 | return String.format(format, number, title); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/MulticolorLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.ConcurrentMap; 10 | import org.apache.log4j.EnhancedPatternLayout; 11 | import org.apache.log4j.Level; 12 | import org.apache.log4j.spi.LoggingEvent; 13 | 14 | /** 15 | * Multi-color layout for LOG4J. 16 | * 17 | *

Use it in your LOG4J configuration: 18 | * 19 | *

 log4j.rootLogger=INFO, CONSOLE
 20 |  * log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
 21 |  * log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout
 22 |  * log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%-5p}] %c: %m%n
23 | * 24 | *

The part of the message wrapped with {@code %color{...}} 25 | * will change its color according to the logging level of the event. Without 26 | * this highlighting the behavior of the layout is identical to 27 | * {@link EnhancedPatternLayout}. You can use {@code %color-red{...}} if you 28 | * want to use specifically red color for the wrapped piece of text. Supported 29 | * colors are: {@code red}, {@code blue}, {@code yellow}, {@code cyan}, 30 | * {@code black}, and {@code white}. 31 | * 32 | *

Besides that you can specify any ANSI color you like with 33 | * {@code %color-;;{...}}, where 34 | * {@code } is a binary mask of attributes, 35 | * {@code } is a background color, and 36 | * {@code } is a foreground color. Read more about 37 | * ANSI escape code. 38 | * 39 | *

This class or its parents are not serializable. 40 | * 41 | *

Maven dependency for this class is 42 | * (see How 43 | * to use with Maven instructions): 44 | * 45 | *

<dependency>
 46 |  *  <groupId>com.jcabi</groupId>
 47 |  *  <artifactId>jcabi-log</artifactId>
 48 |  * </dependency>
49 | * @since 0.1.10 50 | * @see ANSI escape code 51 | * @see PatternLayout from LOG4J 52 | * @see How to use with Maven 53 | */ 54 | @SuppressWarnings("PMD.NonStaticInitializer") 55 | public final class MulticolorLayout extends EnhancedPatternLayout { 56 | 57 | /** 58 | * Name of the property that is used to disable log coloring. 59 | */ 60 | private static final String COLORING_PROPERY = "com.jcabi.log.coloring"; 61 | 62 | /** 63 | * Colors of levels. 64 | */ 65 | private final transient ConcurrentMap levels = 66 | MulticolorLayout.levelMap(); 67 | 68 | /** 69 | * Store original conversation pattern to be able 70 | * to recalculate it, if new colors are provided. 71 | */ 72 | private transient String base; 73 | 74 | /** 75 | * Color human readable data. 76 | */ 77 | private final transient Colors colors = new Colors(); 78 | 79 | @Override 80 | public void setConversionPattern(final String pattern) { 81 | this.base = pattern; 82 | super.setConversionPattern( 83 | new ConversionPattern(this.base, this.colors).generate() 84 | ); 85 | } 86 | 87 | /** 88 | * Allow to overwrite or specify new ANSI color names 89 | * in a javascript map like format. 90 | * 91 | * @param cols JavaScript like map of color names 92 | * @since 0.9 93 | */ 94 | @SuppressWarnings("PMD.UseConcurrentHashMap") 95 | public void setColors(final String cols) { 96 | final Map parsed = new ParseableInformation( 97 | cols 98 | ).information(); 99 | for (final Map.Entry entry : parsed.entrySet()) { 100 | this.colors.addColor(entry.getKey(), entry.getValue()); 101 | } 102 | if (this.base != null) { 103 | this.setConversionPattern(this.base); 104 | } 105 | } 106 | 107 | /** 108 | * Allow to overwrite the ANSI color values for the log levels 109 | * in a javascript map like format. 110 | * @param lev JavaScript like map of levels 111 | * @since 0.9 112 | */ 113 | @SuppressWarnings("PMD.UseConcurrentHashMap") 114 | public void setLevels(final String lev) { 115 | final Map parsed = new ParseableLevelInformation( 116 | lev 117 | ).information(); 118 | for (final Map.Entry entry : parsed.entrySet()) { 119 | this.levels.put(entry.getKey(), entry.getValue()); 120 | } 121 | } 122 | 123 | @Override 124 | public String format(final LoggingEvent event) { 125 | final Formatted formatted; 126 | if (MulticolorLayout.isColoringEnabled()) { 127 | formatted = this.colorfulFormatting(event); 128 | } else { 129 | formatted = this.dullFormatting(event); 130 | } 131 | return formatted.format(); 132 | } 133 | 134 | /** 135 | * Generate a dull {@code Formatted}. 136 | * @param event Event to be formatted 137 | * @return A {@link Formatted} to format the event 138 | * @checkstyle NonStaticMethodCheck (10 lines) 139 | */ 140 | private Formatted dullFormatting(final LoggingEvent event) { 141 | return new DullyFormatted(super.format(event)); 142 | } 143 | 144 | /** 145 | * Generate a colorful {@code Formatted}. 146 | * @param event Event to be formatted 147 | * @return Text of a log event, probably colored with ANSI color codes 148 | */ 149 | private Formatted colorfulFormatting(final LoggingEvent event) { 150 | return new ColorfullyFormatted( 151 | super.format(event), 152 | this.levels.get(event.getLevel().toString()) 153 | ); 154 | } 155 | 156 | /** 157 | * Level map. 158 | * @return Map of levels 159 | */ 160 | private static ConcurrentMap levelMap() { 161 | final ConcurrentMap map = new ConcurrentHashMap<>(0); 162 | map.put(Level.TRACE.toString(), "2;33"); 163 | map.put(Level.DEBUG.toString(), "2;37"); 164 | map.put(Level.INFO.toString(), "0;37"); 165 | map.put(Level.WARN.toString(), "0;33"); 166 | map.put(Level.ERROR.toString(), "0;31"); 167 | map.put(Level.FATAL.toString(), "0;35"); 168 | return map; 169 | } 170 | 171 | /** 172 | * Should the logged text be colored or not. 173 | * @return True if the coloring is enabled, or false otherwise. 174 | */ 175 | private static boolean isColoringEnabled() { 176 | return !"false".equals( 177 | System.getProperty(MulticolorLayout.COLORING_PROPERY) 178 | ); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/NanoDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.FormattableFlags; 9 | import java.util.Formatter; 10 | 11 | /** 12 | * Decorate time interval in nanoseconds. 13 | * 14 | *

For example: 15 | * 16 | *

 17 |  * final long start = System.nanoTime();
 18 |  * // some operations
 19 |  * Logger.debug("completed in %[nano]s", System.nanoTime() - start);
 20 |  * 
21 | * 22 | * @since 0.1 23 | */ 24 | final class NanoDecor implements Formattable { 25 | 26 | /** 27 | * The period to work with, in nanoseconds. 28 | */ 29 | private final transient Double nano; 30 | 31 | /** 32 | * Public ctor. 33 | * @param nan The interval in nanoseconds 34 | */ 35 | @SuppressWarnings( 36 | { 37 | "PMD.NullAssignment", 38 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors" 39 | } 40 | ) 41 | NanoDecor(final Long nan) { 42 | if (nan == null) { 43 | this.nano = null; 44 | } else { 45 | this.nano = Double.valueOf(nan); 46 | } 47 | } 48 | 49 | // @checkstyle ParameterNumber (4 lines) 50 | @Override 51 | public void formatTo(final Formatter formatter, final int flags, 52 | final int width, final int precision) { 53 | if (this.nano == null) { 54 | formatter.format("NULL"); 55 | } else { 56 | final StringBuilder format = new StringBuilder(0); 57 | format.append('%'); 58 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags 59 | .LEFT_JUSTIFY) { 60 | format.append('-'); 61 | } 62 | if (width > 0) { 63 | format.append(width); 64 | } 65 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags 66 | .UPPERCASE) { 67 | format.append('S'); 68 | } else { 69 | format.append('s'); 70 | } 71 | formatter.format(format.toString(), this.toText(precision)); 72 | } 73 | } 74 | 75 | /** 76 | * Create text. 77 | * @param precision The precision 78 | * @return The text 79 | */ 80 | private String toText(final int precision) { 81 | final double number; 82 | final String title; 83 | if (this.nano < 1000.0) { 84 | number = this.nano; 85 | title = "ns"; 86 | } else if (this.nano < (double) (1000L * 1000L)) { 87 | number = this.nano / 1000.0; 88 | title = "µs"; 89 | } else if (this.nano < (double) (1000L * 1000L * 1000L)) { 90 | number = this.nano / (double) (1000L * 1000L); 91 | title = "ms"; 92 | } else if (this.nano < (double) (1000L * 1000L * 1000L * 60L)) { 93 | number = this.nano / (double) (1000L * 1000L * 1000L); 94 | title = "s"; 95 | } else { 96 | number = this.nano / (double) (1000L * 1000L * 1000L * 60L); 97 | title = "min"; 98 | } 99 | final String format; 100 | if (precision >= 0) { 101 | format = String.format("%%.%df%%s", precision); 102 | } else { 103 | format = "%.0f%s"; 104 | } 105 | return String.format(format, number, title); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ObjectDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.lang.reflect.Field; 8 | import java.security.PrivilegedAction; 9 | import java.util.Arrays; 10 | import java.util.Formattable; 11 | import java.util.Formatter; 12 | 13 | /** 14 | * Format internal structure of an object. 15 | * @since 0.1 16 | */ 17 | final class ObjectDecor implements Formattable { 18 | 19 | /** 20 | * The object to work with. 21 | */ 22 | private final transient Object object; 23 | 24 | /** 25 | * Public ctor. 26 | * @param obj The object to format 27 | */ 28 | ObjectDecor(final Object obj) { 29 | this.object = obj; 30 | } 31 | 32 | // @checkstyle ParameterNumber (4 lines) 33 | @Override 34 | public void formatTo(final Formatter formatter, final int flags, 35 | final int width, final int precision) { 36 | if (this.object == null) { 37 | formatter.format("NULL"); 38 | } else if (this.object.getClass().isArray()) { 39 | formatter.format( 40 | new ObjectDecor.ArrayFormatAction((Object[]) this.object).run() 41 | ); 42 | } else { 43 | final String output = 44 | new ObjectDecor.ObjectContentsFormatAction(this.object).run(); 45 | formatter.format(output); 46 | } 47 | } 48 | 49 | /** 50 | * {@link PrivilegedAction} for obtaining array contents. 51 | * 52 | * @since 0.1 53 | */ 54 | private static final class ArrayFormatAction 55 | implements PrivilegedAction { 56 | /** 57 | * Array to format. 58 | */ 59 | private final transient Object[] array; 60 | 61 | /** 62 | * Constructor. 63 | * @param arr Array to format 64 | */ 65 | ArrayFormatAction(final Object... arr) { 66 | this.array = Arrays.copyOf(arr, arr.length); 67 | } 68 | 69 | @Override 70 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 71 | public String run() { 72 | final StringBuilder builder = new StringBuilder("["); 73 | final Formatter formatter = new Formatter(builder); 74 | for (final Object obj : this.array) { 75 | new ObjectDecor(obj).formatTo(formatter, 0, 0, 0); 76 | // @checkstyle MultipleStringLiteralsCheck (1 line) 77 | builder.append(", "); 78 | } 79 | builder.replace(builder.length() - 2, builder.length(), "]"); 80 | return builder.toString(); 81 | } 82 | } 83 | 84 | /** 85 | * {@link PrivilegedAction} for obtaining object contents. 86 | * 87 | * @since 0.1 88 | */ 89 | private static final class ObjectContentsFormatAction 90 | implements PrivilegedAction { 91 | /** 92 | * Object to format. 93 | */ 94 | private final transient Object object; 95 | 96 | /** 97 | * Constructor. 98 | * @param obj Object to format 99 | */ 100 | ObjectContentsFormatAction(final Object obj) { 101 | this.object = obj; 102 | } 103 | 104 | @Override 105 | public String run() { 106 | final StringBuilder builder = new StringBuilder("{"); 107 | for (final Field field 108 | : this.object.getClass().getDeclaredFields()) { 109 | field.setAccessible(true); 110 | try { 111 | builder.append( 112 | String.format( 113 | "%s: \"%s\"", 114 | field.getName(), 115 | field.get(this.object) 116 | ) 117 | ); 118 | } catch (final IllegalAccessException ex) { 119 | throw new IllegalStateException(ex); 120 | } 121 | // @checkstyle MultipleStringLiteralsCheck (1 line) 122 | builder.append(", "); 123 | } 124 | builder.replace(builder.length() - 2, builder.length(), "}"); 125 | return builder.toString(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ParseableInformation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Converts items inside a string like K1:V1,K2:V2 - where K is for key and V 12 | * is for value - to a {@code Map} of string key and string value. 13 | * @since 0.18 14 | */ 15 | class ParseableInformation { 16 | 17 | /** 18 | * Information content to be parsed. 19 | */ 20 | private final transient String content; 21 | 22 | /** 23 | * Construtor. 24 | * @param cont Content to be parsed 25 | */ 26 | ParseableInformation(final String cont) { 27 | super(); 28 | this.content = cont; 29 | } 30 | 31 | /** 32 | * Parse the information. 33 | * @return A {@link Map} with a key,value pair os strings 34 | */ 35 | @SuppressWarnings("PMD.UseConcurrentHashMap") 36 | public final Map information() { 37 | final Map parsed = new HashMap<>(0); 38 | try { 39 | for (final String item : this.items()) { 40 | final String[] values = item.split(":"); 41 | parsed.put(values[0], values[1]); 42 | } 43 | } catch (final ArrayIndexOutOfBoundsException ex) { 44 | throw new IllegalStateException( 45 | String.format(new StringBuilder(0) 46 | .append("Information is not using the pattern ") 47 | .append("KEY1:VALUE,KEY2:VALUE %s") 48 | .toString(), 49 | this.content 50 | ), ex 51 | ); 52 | } 53 | return parsed; 54 | } 55 | 56 | /** 57 | * Split the information using {@link ParseableInformation#SPLIT_ITEMS} 58 | * pattern. 59 | * @return An array of items 60 | */ 61 | private String[] items() { 62 | return this.content.split(","); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/ParseableLevelInformation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.HashMap; 8 | import java.util.Locale; 9 | import java.util.Map; 10 | import org.apache.log4j.Level; 11 | 12 | /** 13 | * Parse information like {@code ParseInformation} does, but increments with 14 | * some extra checks for {@code Level}s. 15 | * @since 0.18 16 | */ 17 | class ParseableLevelInformation { 18 | 19 | /** 20 | * Information content to be parsed. 21 | */ 22 | private final transient String content; 23 | 24 | /** 25 | * Construtor. 26 | * @param cont Content to be parsed 27 | */ 28 | ParseableLevelInformation(final String cont) { 29 | this.content = cont; 30 | } 31 | 32 | /** 33 | * Parse the level information. 34 | * @return A {@link Map} with key,value pair of strings 35 | */ 36 | @SuppressWarnings("PMD.UseConcurrentHashMap") 37 | public final Map information() { 38 | final Map parsed = new ParseableInformation( 39 | this.content 40 | ).information(); 41 | final Map converted = new HashMap<>(0); 42 | for (final Map.Entry entry : parsed.entrySet()) { 43 | final String level = entry.getKey().toUpperCase(Locale.ENGLISH); 44 | if (Level.toLevel(level, null) == null) { 45 | throw new IllegalStateException( 46 | String.format(Locale.ENGLISH, "Unknown level '%s'", level) 47 | ); 48 | } 49 | converted.put(level, entry.getValue()); 50 | } 51 | return converted; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/PreFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.concurrent.CopyOnWriteArrayList; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Processor of formatting string and arguments, before sending it to 15 | * {@link String#format(String,Object[])}. 16 | * 17 | * @since 0.1 18 | */ 19 | final class PreFormatter { 20 | 21 | /** 22 | * Pattern used for matching format string arguments. 23 | */ 24 | private static final Pattern PATTERN = Pattern.compile( 25 | "%(\\d+\\$)?(\\[([A-Za-z\\-.0-9]+)])?[+\\-]?(?:\\d*(?:\\.\\d+)?)?[a-zA-Z%]" 26 | ); 27 | 28 | /** 29 | * List of no argument specifier. 30 | */ 31 | private static final List NO_ARG_SPECIFIERS = 32 | Arrays.asList("%n", "%%"); 33 | 34 | /** 35 | * The formatting string. 36 | */ 37 | private transient String format; 38 | 39 | /** 40 | * List of arguments. 41 | */ 42 | private transient List arguments; 43 | 44 | /** 45 | * Public ctor. 46 | * @param fmt The formatting string 47 | * @param args The list of arguments 48 | */ 49 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") 50 | PreFormatter(final String fmt, final Object... args) { 51 | this.process(fmt, args); 52 | } 53 | 54 | /** 55 | * Get new formatting string. 56 | * @return The formatting text 57 | */ 58 | public String getFormat() { 59 | return this.format; 60 | } 61 | 62 | /** 63 | * Get new list of arguments. 64 | * @return The list of arguments 65 | */ 66 | public Object[] getArguments() { 67 | return this.arguments.toArray(new Object[this.arguments.size()]); 68 | } 69 | 70 | /** 71 | * Process the data provided. 72 | * @param fmt The formatting string 73 | * @param args The list of arguments 74 | * @checkstyle ExecutableStatementCountCheck (100 lines) 75 | */ 76 | @SuppressWarnings("PMD.ConfusingTernary") 77 | private void process(final CharSequence fmt, final Object... args) { 78 | this.arguments = new CopyOnWriteArrayList<>(); 79 | final StringBuffer buf = new StringBuffer(fmt.length()); 80 | final Matcher matcher = PreFormatter.PATTERN.matcher(fmt); 81 | int pos = 0; 82 | while (matcher.find()) { 83 | final String group = matcher.group(); 84 | if (PreFormatter.NO_ARG_SPECIFIERS.contains(group)) { 85 | matcher.appendReplacement( 86 | buf, Matcher.quoteReplacement(group) 87 | ); 88 | } else { 89 | final String decor = matcher.group(3); 90 | if (matcher.group(1) != null) { 91 | matcher.appendReplacement( 92 | buf, Matcher.quoteReplacement(group) 93 | ); 94 | --pos; 95 | } else if (decor == null) { 96 | matcher.appendReplacement( 97 | buf, Matcher.quoteReplacement(group) 98 | ); 99 | this.arguments.add(args[pos]); 100 | } else { 101 | matcher.appendReplacement( 102 | buf, 103 | Matcher.quoteReplacement( 104 | group.replace(matcher.group(2), "") 105 | ) 106 | ); 107 | try { 108 | this.arguments.add( 109 | DecorsManager.decor(decor, args[pos]) 110 | ); 111 | } catch (final DecorException ex) { 112 | this.arguments.add( 113 | String.format("[%s]", ex.getMessage()) 114 | ); 115 | } 116 | } 117 | ++pos; 118 | } 119 | } 120 | if (pos < args.length) { 121 | throw new IllegalArgumentException( 122 | String.format( 123 | "There are %d parameter(s) but only %d format argument(s) were provided.", 124 | args.length, 125 | pos 126 | ) 127 | ); 128 | } 129 | matcher.appendTail(buf); 130 | this.format = buf.toString(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/SecretDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.FormattableFlags; 9 | import java.util.Formatter; 10 | 11 | /** 12 | * Decorator of a secret text. 13 | * @since 0.1 14 | */ 15 | final class SecretDecor implements Formattable { 16 | 17 | /** 18 | * The secret to work with. 19 | */ 20 | private final transient String secret; 21 | 22 | /** 23 | * Public ctor. 24 | * @param scrt The secret 25 | */ 26 | @SuppressWarnings( 27 | { 28 | "PMD.NullAssignment", 29 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors" 30 | } 31 | ) 32 | SecretDecor(final Object scrt) { 33 | if (scrt == null) { 34 | this.secret = null; 35 | } else { 36 | this.secret = scrt.toString(); 37 | } 38 | } 39 | 40 | // @checkstyle ParameterNumber (4 lines) 41 | @Override 42 | public void formatTo(final Formatter formatter, final int flags, 43 | final int width, final int precision) { 44 | if (this.secret == null) { 45 | formatter.format("NULL"); 46 | } else { 47 | final StringBuilder fmt = new StringBuilder(10); 48 | fmt.append('%'); 49 | if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0) { 50 | fmt.append('-'); 51 | } 52 | if (width != 0) { 53 | fmt.append(width); 54 | } 55 | if ((flags & FormattableFlags.UPPERCASE) == 0) { 56 | fmt.append('s'); 57 | } else { 58 | fmt.append('S'); 59 | } 60 | formatter.format( 61 | fmt.toString(), 62 | SecretDecor.scramble(this.secret) 63 | ); 64 | } 65 | } 66 | 67 | /** 68 | * Scramble it and make unreadable. 69 | * @param text The text to scramble 70 | * @return The result 71 | */ 72 | private static String scramble(final String text) { 73 | final StringBuilder out = new StringBuilder(10); 74 | if (text.isEmpty()) { 75 | out.append('?'); 76 | } else { 77 | out.append(text.charAt(0)); 78 | } 79 | out.append("***"); 80 | if (text.isEmpty()) { 81 | out.append('?'); 82 | } else { 83 | out.append(text.charAt(text.length() - 1)); 84 | } 85 | return out.toString(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/SizeDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.FormattableFlags; 9 | import java.util.Formatter; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ConcurrentMap; 12 | 13 | /** 14 | * Size decorator. 15 | * @since 0.1 16 | */ 17 | final class SizeDecor implements Formattable { 18 | 19 | /** 20 | * Highest power supported by this SizeDecor. 21 | */ 22 | private static final int MAX_POWER = 6; 23 | 24 | /** 25 | * Map of prefixes for powers of 1024. 26 | */ 27 | private static final ConcurrentMap SUFFIXES = 28 | new ConcurrentHashMap<>(0); 29 | 30 | /** 31 | * The size to work with. 32 | */ 33 | private final transient Long size; 34 | 35 | static { 36 | SizeDecor.SUFFIXES.put(0, "b"); 37 | SizeDecor.SUFFIXES.put(1, "Kb"); 38 | SizeDecor.SUFFIXES.put(2, "Mb"); 39 | SizeDecor.SUFFIXES.put(3, "Gb"); 40 | SizeDecor.SUFFIXES.put(4, "Tb"); 41 | SizeDecor.SUFFIXES.put(5, "Pb"); 42 | SizeDecor.SUFFIXES.put(6, "Eb"); 43 | } 44 | 45 | /** 46 | * Public ctor. 47 | * @param sze The size 48 | */ 49 | SizeDecor(final Long sze) { 50 | this.size = sze; 51 | } 52 | 53 | // @checkstyle ParameterNumber (4 lines) 54 | @Override 55 | public void formatTo(final Formatter formatter, final int flags, 56 | final int width, final int precision) { 57 | if (this.size == null) { 58 | formatter.format("NULL"); 59 | } else { 60 | final StringBuilder format = new StringBuilder().append('%'); 61 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags 62 | .LEFT_JUSTIFY) { 63 | format.append('-'); 64 | } 65 | if (width > 0) { 66 | format.append(width); 67 | } 68 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags 69 | .UPPERCASE) { 70 | format.append('S'); 71 | } else { 72 | format.append('s'); 73 | } 74 | formatter.format( 75 | format.toString(), this.formatSizeWithSuffix(precision) 76 | ); 77 | } 78 | } 79 | 80 | /** 81 | * Format the size, with suffix. 82 | * @param precision The precision to use 83 | * @return The formatted size 84 | */ 85 | private String formatSizeWithSuffix(final int precision) { 86 | int power = 0; 87 | double number = this.size; 88 | while (number / 1024.0 >= 1.0 && power < SizeDecor.MAX_POWER) { 89 | number /= 1024.0; 90 | power += 1; 91 | } 92 | final String suffix = SizeDecor.SUFFIXES.get(power); 93 | final String format; 94 | if (precision >= 0) { 95 | format = String.format("%%.%df%%s", precision); 96 | } else { 97 | format = "%.0f%s"; 98 | } 99 | return String.format(format, number, suffix); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/Supplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Functional interface used as assignment target for Java8 lambda expressions 9 | * or method references. Can be used for method referencing when the method 10 | * signature respects the following: returns something and takes no arguments. 11 | * 12 | * @param The type of results supplied by this supplier 13 | * @since 0.18 14 | */ 15 | public interface Supplier { 16 | 17 | /** 18 | * Gets a result. 19 | * @return A result 20 | */ 21 | T get(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/SupplierLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | /** 8 | * Logging methods which take {@link Supplier} arguments. 9 | * Used with Java 8 method referencing. 10 | * @since 0.18 11 | * @checkstyle HideUtilityClassConstructorCheck (500 lines) 12 | */ 13 | @SuppressWarnings({ "PMD.ProhibitPublicStaticMethods", "PMD.UseUtilityClass" }) 14 | final class SupplierLogger { 15 | 16 | /** 17 | * Log one message, with {@code TRACE} priority level. 18 | * @param source The source of the logging operation 19 | * @param msg The text message to be logged, with meta-tags 20 | * @param args List of {@link Supplier} arguments. Objects are going 21 | * to be extracted from them and used for log message interpolation 22 | */ 23 | public static void trace( 24 | final Object source, final String msg, final Supplier... args) { 25 | if (Logger.isTraceEnabled(source)) { 26 | Logger.traceForced(source, msg, SupplierLogger.supplied(args)); 27 | } 28 | } 29 | 30 | /** 31 | * Log one message, with {@code DEBUG} priority level. 32 | * @param source The source of the logging operation 33 | * @param msg The text message to be logged, with meta-tags 34 | * @param args List of {@link Supplier} arguments. Objects are going 35 | * to be extracted from them and used for log message interpolation 36 | */ 37 | public static void debug( 38 | final Object source, final String msg, final Supplier... args) { 39 | if (Logger.isDebugEnabled(source)) { 40 | Logger.debugForced(source, msg, SupplierLogger.supplied(args)); 41 | } 42 | } 43 | 44 | /** 45 | * Log one message, with {@code INFO} priority level. 46 | * @param source The source of the logging operation 47 | * @param msg The text message to be logged, with meta-tags 48 | * @param args List of {@link Supplier} arguments. Objects are going 49 | * to be extracted from them and used for log message interpolation 50 | */ 51 | public static void info( 52 | final Object source, final String msg, final Supplier... args) { 53 | if (Logger.isInfoEnabled(source)) { 54 | Logger.infoForced(source, msg, SupplierLogger.supplied(args)); 55 | } 56 | } 57 | 58 | /** 59 | * Log one message, with {@code WARN} priority level. 60 | * @param source The source of the logging operation 61 | * @param msg The text message to be logged, with meta-tags 62 | * @param args List of {@link Supplier} arguments. Objects are going 63 | * to be extracted from them and used for log message interpolation 64 | */ 65 | public static void warn( 66 | final Object source, final String msg, final Supplier... args) { 67 | if (Logger.isWarnEnabled(source)) { 68 | Logger.warnForced(source, msg, SupplierLogger.supplied(args)); 69 | } 70 | } 71 | 72 | /** 73 | * Return the results of the given suppliers. 74 | * @param args Suppliers 75 | * @return Object array 76 | */ 77 | private static Object[] supplied(final Supplier... args) { 78 | final Object[] supplied = new Object[args.length]; 79 | for (int idx = 0; idx < supplied.length; ++idx) { 80 | supplied[idx] = args[idx].get(); 81 | } 82 | return supplied; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/TextDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.Formatter; 9 | 10 | /** 11 | * Decorator of a text. 12 | * 13 | *

For example: 14 | * 15 | *

public void func(Object input) {
16 |  *   Logger.debug("Long input '%[text]s' provided", input);
17 |  * }
18 | * 19 | * @since 0.1.5 20 | */ 21 | final class TextDecor implements Formattable { 22 | 23 | /** 24 | * Maximum length to show. 25 | */ 26 | public static final int MAX = 100; 27 | 28 | /** 29 | * The object. 30 | */ 31 | private final transient Object object; 32 | 33 | /** 34 | * Public ctor. 35 | * @param obj The object 36 | */ 37 | TextDecor(final Object obj) { 38 | this.object = obj; 39 | } 40 | 41 | // @checkstyle ParameterNumber (4 lines) 42 | @Override 43 | public void formatTo(final Formatter formatter, final int flags, 44 | final int width, final int precision) { 45 | if (this.object == null) { 46 | formatter.format("NULL"); 47 | } else { 48 | formatter.format("%s", TextDecor.pretty(this.object.toString())); 49 | } 50 | } 51 | 52 | /** 53 | * Make it look pretty. 54 | * @param text The text to prettify 55 | * @return The result 56 | */ 57 | @SuppressWarnings("PMD.ConsecutiveAppendsShouldReuse") 58 | private static String pretty(final String text) { 59 | final String result; 60 | if (text.length() < TextDecor.MAX) { 61 | result = text; 62 | } else { 63 | final int skip = text.length() - TextDecor.MAX; 64 | final StringBuilder output = new StringBuilder(text.length()); 65 | output.append(text.substring(0, (text.length() - skip) / 2)); 66 | output.append("..").append(skip).append(".."); 67 | output.append( 68 | text.substring(text.length() - TextDecor.MAX + output.length()) 69 | ); 70 | result = output.toString(); 71 | } 72 | return result.replace("\n", "\\n"); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/TypeDecor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.Formatter; 9 | 10 | /** 11 | * Decorator of a type. 12 | * 13 | *

For example: 14 | * 15 | *

16 |  * public void func(Object input) {
17 |  *   Logger.debug("Input of type %[type]s provided", input);
18 |  * }
19 |  * 
20 | * 21 | * @since 0.1 22 | */ 23 | final class TypeDecor implements Formattable { 24 | 25 | /** 26 | * The object. 27 | */ 28 | private final transient Object object; 29 | 30 | /** 31 | * Public ctor. 32 | * @param obj The object 33 | */ 34 | TypeDecor(final Object obj) { 35 | this.object = obj; 36 | } 37 | 38 | // @checkstyle ParameterNumber (4 lines) 39 | @Override 40 | public void formatTo(final Formatter formatter, final int flags, 41 | final int width, final int precision) { 42 | if (this.object == null) { 43 | formatter.format("NULL"); 44 | } else { 45 | formatter.format("%s", this.object.getClass().getName()); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/VerboseCallable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Wrapper of {@link Callable}, that logs all uncaught runtime exceptions. 12 | * 13 | *

You can use it with scheduled executor, for example: 14 | * 15 | *

 Executors.newFixedThreadPool(1).submit(
 16 |  *   new VerboseCallable(callable, true)
 17 |  * );
18 | * 19 | *

Now, every runtime exception that is not caught inside your 20 | * {@link Callable} will be reported to log (using {@link Logger}). 21 | * Two-arguments constructor can be used when you need to instruct the class 22 | * about what to do with the exception: either swallow it or escalate. 23 | * Sometimes it's very important to swallow exceptions. Otherwise an entire 24 | * thread may get stuck (like in the example above). 25 | * 26 | *

This class is thread-safe. 27 | * 28 | * @since 0.16 29 | * @param Type of result 30 | * @see VerboseThreads 31 | * @link Java theory and practice: Dealing with InterruptedException 32 | */ 33 | @SuppressWarnings("PMD.DoNotUseThreads") 34 | public final class VerboseCallable implements Callable { 35 | 36 | /** 37 | * Original runnable. 38 | */ 39 | private final transient Callable origin; 40 | 41 | /** 42 | * Rethrow exceptions (TRUE) or swallow them? 43 | */ 44 | private final transient boolean rethrow; 45 | 46 | /** 47 | * Shall we report a full stacktrace? 48 | */ 49 | private final transient boolean verbose; 50 | 51 | /** 52 | * Default constructor, doesn't swallow exceptions. 53 | * @param callable Callable to wrap 54 | */ 55 | public VerboseCallable(final Callable callable) { 56 | this(callable, false); 57 | } 58 | 59 | /** 60 | * Default constructor, doesn't swallow exceptions. 61 | * @param callable Callable to wrap 62 | * @param swallow Shall we swallow exceptions 63 | * ({@code TRUE}) or re-throw 64 | * ({@code FALSE})? Exception swallowing means that {@link #call()} 65 | * will never throw any exceptions (in any case all exceptions are logged 66 | * using {@link Logger}. 67 | */ 68 | public VerboseCallable(final Callable callable, final boolean swallow) { 69 | this(callable, swallow, true); 70 | } 71 | 72 | /** 73 | * Default constructor. 74 | * @param runnable Runnable to wrap 75 | * @param swallow Shall we swallow exceptions 76 | * ({@code TRUE}) or re-throw 77 | * ({@code FALSE})? Exception swallowing means that {@link #call()} 78 | * will never throw any exceptions (in any case all exceptions are logged 79 | * using {@link Logger}. 80 | * @param vrbs Shall we report the entire 81 | * stacktrace of the exception 82 | * ({@code TRUE}) or just its message in one line ({@code FALSE}) 83 | */ 84 | @SuppressWarnings("PMD.AvoidCatchingGenericException") 85 | public VerboseCallable(final Runnable runnable, 86 | final boolean swallow, final boolean vrbs) { 87 | this( 88 | new Callable() { 89 | @Override 90 | public T call() { 91 | runnable.run(); 92 | return null; 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | return runnable.toString(); 98 | } 99 | }, 100 | swallow, 101 | vrbs 102 | ); 103 | } 104 | 105 | /** 106 | * Default constructor, with configurable behavior for exceptions. 107 | * @param runnable Runnable to wrap 108 | * @param swallow Shall we swallow exceptions 109 | * ({@code TRUE}) or re-throw 110 | * ({@code FALSE})? Exception swallowing means that {@link #call()} 111 | * will never throw any exceptions (in any case all exceptions are logged 112 | * using {@link Logger}. 113 | */ 114 | public VerboseCallable(final Runnable runnable, final boolean swallow) { 115 | this(runnable, swallow, true); 116 | } 117 | 118 | /** 119 | * Default constructor, with fully configurable behavior. 120 | * @param callable Runnable to wrap 121 | * @param swallow Shall we swallow exceptions 122 | * ({@code TRUE}) or re-throw 123 | * ({@code FALSE})? Exception swallowing means that {@link #call()} 124 | * will never throw any exceptions (in any case all exceptions are logged 125 | * using {@link Logger}. 126 | * @param vrbs Shall we report the entire 127 | * stacktrace of the exception 128 | * ({@code TRUE}) or just its message in one line ({@code FALSE}) 129 | */ 130 | @SuppressWarnings("PMD.BooleanInversion") 131 | public VerboseCallable(final Callable callable, 132 | final boolean swallow, final boolean vrbs) { 133 | this.origin = callable; 134 | this.rethrow = !swallow; 135 | this.verbose = vrbs; 136 | } 137 | 138 | @Override 139 | public String toString() { 140 | return this.origin.toString(); 141 | } 142 | 143 | @Override 144 | @SuppressWarnings("PMD.AvoidCatchingGenericException") 145 | public T call() throws Exception { 146 | T result = null; 147 | try { 148 | result = this.origin.call(); 149 | // @checkstyle IllegalCatch (1 line) 150 | } catch (final RuntimeException ex) { 151 | if (this.rethrow) { 152 | Logger.warn( 153 | this, "Escalated runtime exception: %s", this.tail(ex) 154 | ); 155 | throw ex; 156 | } 157 | Logger.warn(this, "Swallowed runtime exception: %s", this.tail(ex)); 158 | // @checkstyle IllegalCatch (1 line) 159 | } catch (final Exception ex) { 160 | if (this.rethrow) { 161 | Logger.warn(this, "Escalated exception: %s", this.tail(ex)); 162 | throw ex; 163 | } 164 | Logger.warn(this, "Swallowed exception: %s", this.tail(ex)); 165 | // @checkstyle IllegalCatch (1 line) 166 | } catch (final Error error) { 167 | if (this.rethrow) { 168 | Logger.error(this, "Escalated error: %s", this.tail(error)); 169 | throw error; 170 | } 171 | Logger.error(this, "Swallowed error: %s", this.tail(error)); 172 | } 173 | try { 174 | TimeUnit.MICROSECONDS.sleep(1L); 175 | } catch (final InterruptedException ex) { 176 | Thread.currentThread().interrupt(); 177 | throw new IllegalStateException(ex); 178 | } 179 | return result; 180 | } 181 | 182 | /** 183 | * Make a tail of the error/warning message, using the exception thrown. 184 | * @param throwable The exception/error caught 185 | * @return The message to show in logs 186 | */ 187 | private String tail(final Throwable throwable) { 188 | final String tail; 189 | if (this.verbose) { 190 | tail = Logger.format("%[exception]s", throwable); 191 | } else { 192 | tail = Logger.format( 193 | "%[type]s('%s')", 194 | throwable, 195 | throwable.getMessage() 196 | ); 197 | } 198 | return tail; 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/VerboseRunnable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.Callable; 8 | 9 | /** 10 | * Wrapper of {@link Runnable}, that logs all uncaught runtime exceptions. 11 | * 12 | *

You can use it with scheduled executor, for example: 13 | * 14 | *

 Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
 15 |  *   new VerboseRunnable(runnable, true), 1L, 1L, TimeUnit.SECONDS
 16 |  * );
17 | * 18 | *

Now, every runtime exception that is not caught inside your 19 | * {@link Runnable} will be reported to log (using {@link Logger}). 20 | * Two-arguments constructor can be used when you need to instruct the class 21 | * about what to do with the exception: either swallow it or escalate. 22 | * Sometimes it's very important to swallow exceptions. Otherwise an entire 23 | * thread may get stuck (like in the example above). 24 | * 25 | *

This class is thread-safe. 26 | * 27 | * @since 0.1.3 28 | * @see VerboseThreads 29 | * @link Java theory and practice: Dealing with InterruptedException 30 | */ 31 | @SuppressWarnings("PMD.DoNotUseThreads") 32 | public final class VerboseRunnable implements Runnable { 33 | 34 | /** 35 | * Original runnable. 36 | */ 37 | private final transient Runnable origin; 38 | 39 | /** 40 | * Rethrow exceptions (TRUE) or swallow them? 41 | */ 42 | private final transient boolean rethrow; 43 | 44 | /** 45 | * Shall we report a full stacktrace? 46 | */ 47 | private final transient boolean verbose; 48 | 49 | /** 50 | * Default constructor, doesn't swallow exceptions. 51 | * @param runnable Runnable to wrap 52 | */ 53 | public VerboseRunnable(final Runnable runnable) { 54 | this(runnable, false); 55 | } 56 | 57 | /** 58 | * Default constructor, doesn't swallow exceptions. 59 | * @param callable Callable to wrap 60 | * @since 0.7.17 61 | */ 62 | public VerboseRunnable(final Callable callable) { 63 | this(callable, false); 64 | } 65 | 66 | /** 67 | * Default constructor, doesn't swallow exceptions. 68 | * @param callable Callable to wrap 69 | * @param swallow Shall we swallow exceptions 70 | * ({@code TRUE}) or re-throw 71 | * ({@code FALSE})? Exception swallowing means that {@link #run()} 72 | * will never throw any exceptions (in any case all exceptions are logged 73 | * using {@link Logger}. 74 | * @since 0.1.10 75 | */ 76 | public VerboseRunnable(final Callable callable, final boolean swallow) { 77 | this(callable, swallow, true); 78 | } 79 | 80 | /** 81 | * Default constructor. 82 | * @param callable Callable to wrap 83 | * @param swallow Shall we swallow exceptions 84 | * ({@code TRUE}) or re-throw 85 | * ({@code FALSE})? Exception swallowing means that {@link #run()} 86 | * will never throw any exceptions (in any case all exceptions are logged 87 | * using {@link Logger}. 88 | * @param vrbs Shall we report the entire 89 | * stacktrace of the exception 90 | * ({@code TRUE}) or just its message in one line ({@code FALSE}) 91 | * @since 0.7.17 92 | */ 93 | @SuppressWarnings("PMD.AvoidCatchingGenericException") 94 | public VerboseRunnable(final Callable callable, 95 | final boolean swallow, final boolean vrbs) { 96 | this( 97 | new Runnable() { 98 | @Override 99 | public void run() { 100 | try { 101 | callable.call(); 102 | } catch (final InterruptedException ex) { 103 | Thread.currentThread().interrupt(); 104 | throw new IllegalStateException(ex); 105 | // @checkstyle IllegalCatch (1 line) 106 | } catch (final Exception ex) { 107 | throw new IllegalStateException(ex); 108 | } 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | return callable.toString(); 114 | } 115 | }, 116 | swallow, 117 | vrbs 118 | ); 119 | } 120 | 121 | /** 122 | * Default constructor, with configurable behavior for exceptions. 123 | * @param runnable Runnable to wrap 124 | * @param swallow Shall we swallow exceptions 125 | * ({@code TRUE}) or re-throw 126 | * ({@code FALSE})? Exception swallowing means that {@link #run()} 127 | * will never throw any exceptions (in any case all exceptions are logged 128 | * using {@link Logger}. 129 | * @since 0.1.4 130 | */ 131 | public VerboseRunnable(final Runnable runnable, final boolean swallow) { 132 | this(runnable, swallow, true); 133 | } 134 | 135 | /** 136 | * Default constructor, with fully configurable behavior. 137 | * @param runnable Runnable to wrap 138 | * @param swallow Shall we swallow exceptions 139 | * ({@code TRUE}) or re-throw 140 | * ({@code FALSE})? Exception swallowing means that {@link #run()} 141 | * will never throw any exceptions (in any case all exceptions are logged 142 | * using {@link Logger}. 143 | * @param vrbs Shall we report the entire 144 | * stacktrace of the exception 145 | * ({@code TRUE}) or just its message in one line ({@code FALSE}) 146 | * @since 0.7.17 147 | */ 148 | @SuppressWarnings("PMD.BooleanInversion") 149 | public VerboseRunnable(final Runnable runnable, 150 | final boolean swallow, final boolean vrbs) { 151 | this.origin = runnable; 152 | this.rethrow = !swallow; 153 | this.verbose = vrbs; 154 | } 155 | 156 | @Override 157 | public String toString() { 158 | return this.origin.toString(); 159 | } 160 | 161 | @Override 162 | @SuppressWarnings("PMD.AvoidCatchingGenericException") 163 | public void run() { 164 | try { 165 | this.origin.run(); 166 | // @checkstyle IllegalCatch (1 line) 167 | } catch (final RuntimeException ex) { 168 | if (this.rethrow) { 169 | Logger.warn(this, "Escalated exception: %s", this.tail(ex)); 170 | throw ex; 171 | } 172 | Logger.warn(this, "Swallowed exception: %s", this.tail(ex)); 173 | // @checkstyle IllegalCatch (1 line) 174 | } catch (final Error error) { 175 | if (this.rethrow) { 176 | Logger.error(this, "Escalated error: %s", this.tail(error)); 177 | throw error; 178 | } 179 | Logger.error(this, "Swallowed error: %s", this.tail(error)); 180 | } 181 | if (Thread.currentThread().isInterrupted()) { 182 | Thread.currentThread().interrupt(); 183 | throw new IllegalStateException( 184 | "The thread has been interrupted" 185 | ); 186 | } 187 | } 188 | 189 | /** 190 | * Make a tail of the error/warning message, using the exception thrown. 191 | * @param throwable The exception/error caught 192 | * @return The message to show in logs 193 | */ 194 | private String tail(final Throwable throwable) { 195 | final String tail; 196 | if (this.verbose) { 197 | tail = Logger.format("%[exception]s", throwable); 198 | } else { 199 | tail = Logger.format( 200 | "%[type]s('%s')", 201 | throwable, 202 | throwable.getMessage() 203 | ); 204 | } 205 | return tail; 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/VerboseThreads.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.ThreadFactory; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * Convenient {@link ThreadFactory}, that logs all uncaught exceptions. 12 | * 13 | *

The factory should be used together 14 | * with executor services from {@code java.util.concurrent} package. Without 15 | * these "verbose" threads your runnable tasks will not report anything to 16 | * console once they die because of a runtime exception, for example: 17 | * 18 | *

 Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
 19 |  *   new Runnable() {
 20 |  *     @Override
 21 |  *     public void run() {
 22 |  *       // some sensitive operation that may throw
 23 |  *       // a runtime exception
 24 |  *     },
 25 |  *     1L, 1L, TimeUnit.SECONDS
 26 |  *   }
 27 |  * );
28 | * 29 | *

The exception in this example will never be caught by nobody. It will 30 | * just terminate current execution of the {@link Runnable} task. Moreover, 31 | * it won't reach any {@link Thread.UncaughtExceptionHandler}, 32 | * because this 33 | * is how {@link java.util.concurrent.ScheduledExecutorService} 34 | * is behaving. This is how we solve 35 | * the problem with {@link VerboseThreads}: 36 | * 37 | *

 ThreadFactory factory = new VerboseThreads();
 38 |  * Executors.newScheduledThreadPool(2, factory).scheduleAtFixedRate(
 39 |  *   new Runnable() {
 40 |  *     @Override
 41 |  *     public void run() {
 42 |  *       // the same sensitive operation that may throw
 43 |  *       // a runtime exception
 44 |  *     },
 45 |  *     1L, 1L, TimeUnit.SECONDS
 46 |  *   }
 47 |  * );
48 | * 49 | *

Now, every runtime exception that is not caught inside your 50 | * {@link Runnable} will be reported to log (using {@link Logger}). 51 | * 52 | *

This class is thread-safe. 53 | * 54 | * @since 0.1.2 55 | * @see VerboseRunnable 56 | */ 57 | @SuppressWarnings("PMD.DoNotUseThreads") 58 | public final class VerboseThreads implements ThreadFactory { 59 | 60 | /** 61 | * Thread group. 62 | */ 63 | private final transient ThreadGroup group; 64 | 65 | /** 66 | * Prefix to use. 67 | */ 68 | private final transient String prefix; 69 | 70 | /** 71 | * Number of the next thread to create. 72 | */ 73 | private final transient AtomicInteger number; 74 | 75 | /** 76 | * Create threads as daemons? 77 | */ 78 | private final transient boolean daemon; 79 | 80 | /** 81 | * Default thread priority. 82 | */ 83 | private final transient int priority; 84 | 85 | /** 86 | * Default constructor ({@code "verbose"} as a prefix, threads are daemons, 87 | * default thread priority is {@code 1}). 88 | */ 89 | public VerboseThreads() { 90 | this("verbose", true, 1); 91 | } 92 | 93 | /** 94 | * Detailed constructor, with a prefix of thread names (threads are daemons, 95 | * default thread priority is {@code 1}). 96 | * @param pfx Prefix for thread names 97 | */ 98 | public VerboseThreads(final String pfx) { 99 | this(pfx, true, 1); 100 | } 101 | 102 | /** 103 | * Detailed constructor, with a prefix of thread names (threads are daemons, 104 | * default thread priority is {@code 1}). 105 | * @param type Prefix will be build from this type name 106 | */ 107 | public VerboseThreads(final Object type) { 108 | this(type.getClass().getSimpleName(), true, 1); 109 | } 110 | 111 | /** 112 | * Detailed constructor, with a prefix of thread names (threads are daemons, 113 | * default thread priority is {@code 1}). 114 | * @param type Prefix will be build from this type name 115 | */ 116 | public VerboseThreads(final Class type) { 117 | this(type.getSimpleName(), true, 1); 118 | } 119 | 120 | /** 121 | * Detailed constructor. 122 | * @param pfx Prefix for thread names 123 | * @param dmn Threads should be daemons? 124 | * @param prt Default priority for all threads 125 | */ 126 | public VerboseThreads(final String pfx, final boolean dmn, 127 | final int prt) { 128 | this.prefix = pfx; 129 | this.daemon = dmn; 130 | this.priority = prt; 131 | this.group = new VerboseThreads.Group(pfx); 132 | this.number = new AtomicInteger(1); 133 | } 134 | 135 | @Override 136 | public Thread newThread(final Runnable runnable) { 137 | final Thread thread = new Thread( 138 | this.group, 139 | new VerboseThreads.Wrap(runnable) 140 | ); 141 | thread.setName( 142 | String.format( 143 | "%s-%d", 144 | this.prefix, 145 | this.number.getAndIncrement() 146 | ) 147 | ); 148 | thread.setDaemon(this.daemon); 149 | thread.setPriority(this.priority); 150 | return thread; 151 | } 152 | 153 | /** 154 | * Group to use. 155 | * 156 | * @since 0.1 157 | */ 158 | private static final class Group extends ThreadGroup { 159 | /** 160 | * Ctor. 161 | * @param name Name of it 162 | */ 163 | Group(final String name) { 164 | super(name); 165 | } 166 | 167 | @Override 168 | public void uncaughtException(final Thread thread, 169 | final Throwable throwable) { 170 | Logger.warn(this, "%[exception]s", throwable); 171 | } 172 | } 173 | 174 | /** 175 | * Runnable decorator. 176 | * 177 | * @since 0.1 178 | */ 179 | private static final class Wrap implements Runnable { 180 | /** 181 | * Origin runnable. 182 | */ 183 | private final transient Runnable origin; 184 | 185 | /** 186 | * Ctor. 187 | * @param runnable Origin runnable 188 | */ 189 | Wrap(final Runnable runnable) { 190 | this.origin = runnable; 191 | } 192 | 193 | @Override 194 | @SuppressWarnings("PMD.AvoidCatchingGenericException") 195 | public void run() { 196 | try { 197 | this.origin.run(); 198 | // @checkstyle IllegalCatch (1 line) 199 | } catch (final RuntimeException ex) { 200 | Logger.warn( 201 | this, 202 | "%s: %[exception]s", 203 | Thread.currentThread().getName(), 204 | ex 205 | ); 206 | throw ex; 207 | // @checkstyle IllegalCatch (1 line) 208 | } catch (final Error error) { 209 | Logger.error( 210 | this, 211 | "%s (error): %[exception]s", 212 | Thread.currentThread().getName(), 213 | error 214 | ); 215 | throw error; 216 | } 217 | } 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/com/jcabi/log/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | /** 7 | * Convenient logging utils. 8 | * 9 | *

The only dependency you need is (check our latest version available 10 | * at www.jcabi.com): 11 | * 12 | *

<depedency>
13 |  *   <groupId>com.jcabi</groupId>
14 |  *   <artifactId>jcabi-log</artifactId>
15 |  * </dependency>
16 | * 17 | * @see project website 18 | */ 19 | package com.jcabi.log; 20 | -------------------------------------------------------------------------------- /src/site/apt/decors.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Logging decors 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-04-29 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Decors 40 | 41 | Log an event like this: 42 | 43 | +-- 44 | import com.jcabi.log.Logger; 45 | class Foo { 46 | void bar(int value) { 47 | final long start = System.nanoTime(); 48 | // some operations 49 | Logger.debug( 50 | this, 51 | "bar(%d): done in %[nano]d", 52 | value, 53 | System.nanoTime() - start 54 | ); 55 | } 56 | } 57 | +-- 58 | 59 | You will see something like this in log: 60 | 61 | --- 62 | [DEBUG] Foo: #bar(1234): done in 3.45ms 63 | --- 64 | 65 | Nano-seconds will be formatted as time through a built-in "decor". 66 | 67 | There are a few other pre-defined decors: 68 | 69 | * <<>> -- converts <<>> to text. 70 | 71 | * <<>> -- exception message and full stacktrace; 72 | 73 | * <<>> -- array or list of elements into text; 74 | 75 | * <<>> -- milliseconds into their text presentation; 76 | 77 | * <<>> -- nanoseconds into their text presentation; 78 | 79 | * <<>> -- makes text readable; 80 | 81 | * <<>> -- type name of the object. 82 | 83 | In order to use your own decor just implement 84 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/Formattable.html}<<>>}}: 85 | 86 | +-- 87 | import com.jcabi.log.Decor; 88 | import java.util.Formattable; 89 | import java.util.Formatter; 90 | public final class DataDecor implements Formattable { 91 | private final transient Data data; 92 | public DataDecor(final Data dat) { 93 | this.data = dat; 94 | } 95 | @Override 96 | public void formatTo(final Formatter formatter, final int flags, 97 | final int width, final int precision) { 98 | formatter.format("%s", this.data.getSomeValueOutOfIt()); 99 | } 100 | } 101 | +-- 102 | 103 | Then, provide its class name in log formatting string, for example: 104 | 105 | +-- 106 | import com.jcabi.log.Logger; 107 | public class Main { 108 | public static void main(String[] args) { 109 | Logger.debug( 110 | this, 111 | "bar(%d): show some data: %[com.example.DataDecor]s", 112 | value, 113 | data 114 | ); 115 | } 116 | } 117 | +-- 118 | -------------------------------------------------------------------------------- /src/site/apt/index.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Static Wrapper of SLF4J 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-04-29 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Static Wrapper of SLF4J 40 | 41 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}} 42 | is a convenient static wrapper of {{{http://www.slf4j.org/}slf4j}} 43 | (don't forget to include one of 44 | {{{http://www.slf4j.org/manual.html#binding}SLF4J Bindings}} 45 | into the project): 46 | 47 | +-- 48 | import com.jcabi.log.Logger; 49 | class Foo { 50 | void bar(int value) { 51 | Logger.debug(this, "method #bar(d) was called", value); 52 | } 53 | } 54 | +-- 55 | 56 | Read also about log {{{./decors.html}decors}} and a convenient 57 | AOP annotation 58 | {{{http://aspects.jcabi.com/annotation-loggable.html}<<<@Loggable>>>}} from 59 | {{{http://aspects.jcabi.com/index.html}jcabi-aspects}}. 60 | 61 | There are a few other convenient classes in this package: 62 | 63 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseRunnable.html}<<>>}}: 64 | wrapper around <<>> that swallows all runtime 65 | exceptions and logs them to SLF4J. 66 | 67 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}}: 68 | wrapper around <<>> that monitors process executions, 69 | collects its output into a <<>> and logs everything through SLF4J. 70 | 71 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}: 72 | an implementation of <<>> that 73 | instantiates threads that log all runtime exceptions through SLF4J. 74 | 75 | The only dependency you need is 76 | (you can also download 77 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}} 78 | and add it to the classpath): 79 | 80 | +-- 81 | 82 | com.jcabi 83 | jcabi-log 84 | ${project.version} 85 | 86 | +-- 87 | 88 | * Cutting Edge Version 89 | 90 | If you want to use current version of the product, you can do it with 91 | this configuration in your <<>>: 92 | 93 | +-- 94 | 95 | 96 | oss.sonatype.org 97 | https://oss.sonatype.org/content/repositories/snapshots/ 98 | 99 | 100 | 101 | 102 | com.jcabi 103 | jcabi-log 104 | 1.0-SNAPSHOT 105 | 106 | 107 | +-- 108 | -------------------------------------------------------------------------------- /src/site/apt/multicolor.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Multicolor Layout for LOG4J 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-08-30 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Multicolor Layout for LOG4J 40 | 41 | Configure it in your <<>>: 42 | 43 | +-- 44 | log4j.rootLogger=INFO, CONSOLE 45 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 46 | log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout 47 | log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%-5p}] %c: %m%n 48 | +-- 49 | 50 | Or in XML config: 51 | 52 | +-- 53 | 54 | 55 | 56 | 57 | 58 | +-- 59 | 60 | In version 0.9 there is an ability to re-defined standard colors for 61 | logging levels. Besides that, it's possible to re-define numeric values 62 | of standard colors, for example: 63 | 64 | +-- 65 | log4j.appender.CONSOLE.layout.Levels=INFO:2;10,WARN:2;32 66 | log4j.appender.CONSOLE.layout.Colors=white:10 67 | +-- 68 | 69 | Or: 70 | 71 | +-- 72 | 73 | 74 | +-- 75 | 76 | Read JavaDoc of 77 | {{{./apidocs-${project.version}/com/jcabi/log/MulticolorLayout.html}<<>>}}. 78 | 79 | The only dependency you need is 80 | (you can also download 81 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}} 82 | and add it to the classpath): 83 | 84 | +-- 85 | 86 | com.jcabi 87 | jcabi-log 88 | ${project.version} 89 | 90 | +-- 91 | -------------------------------------------------------------------------------- /src/site/apt/threads-VerboseProcess.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Process That Logs And Consumes Output 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-12-16 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Process That Logs And Consumes Output 40 | 41 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}} 42 | logs output of a java 43 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html}<<>>}} 44 | and consumes its output at the same time, for example: 45 | 46 | +-- 47 | public class Main { 48 | public static void main(String[] args) { 49 | String name = new VerboseProcess( 50 | new ProcessBuilder("who", "am", "i") 51 | ).stdout(); 52 | System.out.println("I am: " + name); 53 | } 54 | } 55 | +-- 56 | 57 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}} 58 | throws an exception if the process returns a non-zero exit code. 59 | 60 | The only dependency you need is 61 | (you can also download 62 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}} 63 | and add it to the classpath): 64 | 65 | +-- 66 | 67 | com.jcabi 68 | jcabi-log 69 | ${project.version} 70 | 71 | +-- 72 | -------------------------------------------------------------------------------- /src/site/apt/threads-VerboseRunnable.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Runnable That Logs Runtime Exceptions 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-12-16 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Runnable That Logs Runtime Exceptions 40 | 41 | You can use 42 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseRunnable.html}<<>>}}: 43 | with scheduled executor, for example: 44 | 45 | +-- 46 | public class Main { 47 | public static void main(String[] args) { 48 | Executors.newScheduledThreadPool(2).scheduleAtFixedRate( 49 | new VerboseRunnable( 50 | new Runnable() { 51 | @Override 52 | public void run() { 53 | // some operation that may lead to a runtime 54 | // exception, which we want to log through SLF4J 55 | } 56 | } 57 | ), 58 | 1L, 1L, TimeUnit.SECONDS 59 | ); 60 | } 61 | } 62 | +-- 63 | 64 | Now, every runtime exception that is not caught inside your 65 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html}<<>>}} 66 | will be reported to log (using 67 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}}). 68 | 69 | Two-arguments constructor can be used when you need to instruct the class 70 | about what to do with the exception: either swallow it or escalate. 71 | Sometimes it's very important to swallow exceptions. Otherwise an entire 72 | thread may get stuck (like in the example above). 73 | 74 | The only dependency you need is 75 | (you can also download 76 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}} 77 | and add it to the classpath): 78 | 79 | +-- 80 | 81 | com.jcabi 82 | jcabi-log 83 | ${project.version} 84 | 85 | +-- 86 | -------------------------------------------------------------------------------- /src/site/apt/threads-VerboseThreads.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Thread Factory that Logs Exceptions 3 | ------ 4 | Yegor Bugayenko 5 | ------ 6 | 2012-12-16 7 | ------ 8 | 9 | ~~ 10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko 11 | ~~ All rights reserved. 12 | ~~ 13 | ~~ Redistribution and use in source and binary forms, with or without 14 | ~~ modification, are permitted provided that the following conditions 15 | ~~ are met: 1) Redistributions of source code must retain the above 16 | ~~ copyright notice, this list of conditions and the following 17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above 18 | ~~ copyright notice, this list of conditions and the following 19 | ~~ disclaimer in the documentation and/or other materials provided 20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor 21 | ~~ the names of its contributors may be used to endorse or promote 22 | ~~ products derived from this software without specific prior written 23 | ~~ permission. 24 | ~~ 25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE. 37 | ~~ 38 | 39 | Thread Factory that Logs Exceptions 40 | 41 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}} 42 | is an implementation of 43 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadFactory.html}<<>>}} 44 | that instantiates threads that log all runtime exceptions through SLF4J. 45 | 46 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}} 47 | factory should be used together 48 | with executor services from <<>> package. Without 49 | this "verbose" thread factory your runnable tasks will not report anything to 50 | console once they die because of a runtime exception, for example: 51 | 52 | +-- 53 | public class Main { 54 | public static void main(String[] args) { 55 | Executors.newScheduledThreadPool(2).scheduleAtFixedRate( 56 | new Runnable() { 57 | @Override 58 | public void run() { 59 | // some sensitive operation that may throw 60 | // a runtime exception 61 | } 62 | }, 1L, 1L, TimeUnit.SECONDS 63 | ); 64 | } 65 | } 66 | +-- 67 | 68 | The exception in this example will never be caught by nobody. It will 69 | just terminate current execution of the <<>> task. Moreover, 70 | it won't reach any 71 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.UncaughtExceptionHandler.html}<<>>}}, 72 | because this 73 | is how 74 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html}<<>>}} 75 | is behaving by default. This is how we solve 76 | the problem with 77 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}: 78 | 79 | +-- 80 | public class Main { 81 | public static void main(String[] args) { 82 | ThreadFactory factory = new VerboseThreads(); 83 | Executors.newScheduledThreadPool(2, factory).scheduleAtFixedRate( 84 | new Runnable() { 85 | @Override 86 | public void run() { 87 | // the same sensitive operation that may throw 88 | // a runtime exception 89 | } 90 | }, 1L, 1L, TimeUnit.SECONDS 91 | ); 92 | } 93 | } 94 | +-- 95 | 96 | Now, every runtime exception that is not caught inside your 97 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html}<<>>}} 98 | will be reported to log (using 99 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}}). 100 | 101 | The only dependency you need is 102 | (you can also download 103 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}} 104 | and add it to the classpath): 105 | 106 | +-- 107 | 108 | com.jcabi 109 | jcabi-log 110 | ${project.version} 111 | 112 | +-- 113 | -------------------------------------------------------------------------------- /src/site/resources/CNAME: -------------------------------------------------------------------------------- 1 | log.jcabi.com 2 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 33 | com.jcabi 34 | jcabi-maven-skin 35 | 1.7.1 36 | 37 | 38 | jcabi 39 | https://www.jcabi.com/logo-square.svg 40 | https://www.jcabi.com/ 41 | 64 42 | 64 43 | 44 | UA-1963507-23 45 | 46 | 47 | <link href="https://www.jcabi.com/logo-square.svg" rel="shortcut icon"/> 48 | <link href="https://plus.google.com/u/0/114792568016408327418?rel=author" rel="author"/> 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ConversionPatternTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.hamcrest.MatcherAssert; 8 | import org.hamcrest.Matchers; 9 | import org.junit.jupiter.api.Test; 10 | 11 | /** 12 | * Test case for {@link ConversionPattern}. 13 | * @since 0.19 14 | */ 15 | @SuppressWarnings("PMD.AvoidDuplicateLiterals") 16 | final class ConversionPatternTest { 17 | /** 18 | * Control Sequence Indicator. 19 | */ 20 | private static final String CSI = "\u001b\\["; 21 | 22 | /** 23 | * Default color map for testing. 24 | */ 25 | private static final Colors COLORS = new Colors(); 26 | 27 | @Test 28 | void testGenerateNoReplacement() { 29 | MatcherAssert.assertThat( 30 | convert(""), 31 | Matchers.equalTo("") 32 | ); 33 | MatcherAssert.assertThat( 34 | convert("foo"), 35 | Matchers.equalTo("foo") 36 | ); 37 | MatcherAssert.assertThat( 38 | convert("%color"), 39 | Matchers.equalTo("%color") 40 | ); 41 | MatcherAssert.assertThat( 42 | convert("%color-"), 43 | Matchers.equalTo("%color-") 44 | ); 45 | MatcherAssert.assertThat( 46 | convert("%color{%c{1}foo"), 47 | Matchers.equalTo("%color{%c{1}foo") 48 | ); 49 | } 50 | 51 | @Test 52 | void testGenerateEmpty() { 53 | MatcherAssert.assertThat( 54 | convert("%color{}"), 55 | Matchers.equalTo(colorWrap("")) 56 | ); 57 | } 58 | 59 | @Test 60 | void testGenerateSimple() { 61 | MatcherAssert.assertThat( 62 | convert("%color{Hello World}"), 63 | Matchers.equalTo(colorWrap("Hello World")) 64 | ); 65 | MatcherAssert.assertThat( 66 | convert("%color{Hello World}foo"), 67 | Matchers.equalTo(String.format("%sfoo", colorWrap("Hello World"))) 68 | ); 69 | MatcherAssert.assertThat( 70 | convert("%color{Hello}%color{World}"), 71 | Matchers.equalTo( 72 | String.format("%s%s", colorWrap("Hello"), colorWrap("World")) 73 | ) 74 | ); 75 | } 76 | 77 | @Test 78 | void testGenerateCurlyBraces() { 79 | MatcherAssert.assertThat( 80 | ConversionPatternTest.convert("%color{%c{1}}"), 81 | Matchers.equalTo(ConversionPatternTest.colorWrap("%c{1}")) 82 | ); 83 | MatcherAssert.assertThat( 84 | ConversionPatternTest.convert("%color{%c{1}}foo"), 85 | Matchers.equalTo( 86 | String.format("%sfoo", ConversionPatternTest.colorWrap("%c{1}")) 87 | ) 88 | ); 89 | MatcherAssert.assertThat( 90 | ConversionPatternTest.convert("%color{%c1}}foo"), 91 | Matchers.equalTo( 92 | String.format("%s}foo", ConversionPatternTest.colorWrap("%c1")) 93 | ) 94 | ); 95 | MatcherAssert.assertThat( 96 | ConversionPatternTest.convert("%color{%c{{{1}{2}}}}foo"), 97 | Matchers.equalTo( 98 | String.format( 99 | "%sfoo", 100 | ConversionPatternTest.colorWrap("%c{{{1}{2}}}") 101 | ) 102 | ) 103 | ); 104 | } 105 | 106 | /** 107 | * Convenience method to generate conversion pattern for the tests. 108 | * @param pat Pattern to be used 109 | * @return Conversion pattern 110 | */ 111 | private static String convert(final String pat) { 112 | return new ConversionPattern( 113 | pat, 114 | ConversionPatternTest.COLORS 115 | ).generate(); 116 | } 117 | 118 | /** 119 | * Wraps the given string in the expected ANSI color sequence. 120 | * @param str Input string to wrap. 121 | * @return Wrapped string. 122 | */ 123 | private static String colorWrap(final String str) { 124 | return String.format( 125 | "%s?m%s%sm", 126 | ConversionPatternTest.CSI, 127 | str, 128 | ConversionPatternTest.CSI 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/DecorMocker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.Formatter; 9 | 10 | /** 11 | * Primitive decor, for testing only. 12 | * @since 0.1 13 | */ 14 | public final class DecorMocker implements Formattable { 15 | 16 | /** 17 | * The text. 18 | */ 19 | private final transient String text; 20 | 21 | /** 22 | * Public ctor. 23 | * @param txt The text to output 24 | */ 25 | public DecorMocker(final Object txt) { 26 | this.text = txt.toString(); 27 | } 28 | 29 | // @checkstyle ParameterNumber (4 lines) 30 | @Override 31 | public void formatTo(final Formatter formatter, final int flags, 32 | final int width, final int precision) { 33 | formatter.format( 34 | String.format( 35 | "%s [f=%d, w=%d, p=%d]", 36 | this.text, 37 | flags, 38 | width, 39 | precision 40 | ) 41 | ); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/DecorsManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.hamcrest.MatcherAssert; 8 | import org.hamcrest.Matchers; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** 13 | * Test case for {@link DecorsManager}. 14 | * @since 0.1 15 | */ 16 | final class DecorsManagerTest { 17 | 18 | @Test 19 | void hasBuiltInDecors() throws Exception { 20 | MatcherAssert.assertThat( 21 | DecorsManager.decor("nano", 1L), 22 | Matchers.instanceOf(NanoDecor.class) 23 | ); 24 | } 25 | 26 | @Test 27 | void throwsExceptionForAbsentDecor() { 28 | Assertions.assertThrows( 29 | DecorException.class, 30 | () -> DecorsManager.decor("non-existing-formatter", null) 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/DomDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.Formatter; 9 | import javax.xml.parsers.DocumentBuilderFactory; 10 | import org.hamcrest.Matchers; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.Mockito; 13 | import org.mockito.hamcrest.MockitoHamcrest; 14 | import org.w3c.dom.Document; 15 | 16 | /** 17 | * Test case for {@link DomDecor}. 18 | * 19 | * @since 0.1 20 | */ 21 | final class DomDecorTest { 22 | 23 | @Test 24 | void convertsDocumentToText() throws Exception { 25 | final Document doc = DocumentBuilderFactory.newInstance() 26 | .newDocumentBuilder().newDocument(); 27 | doc.appendChild(doc.createElement("root")); 28 | final Formattable decor = new DomDecor(doc); 29 | final Appendable dest = Mockito.mock(Appendable.class); 30 | final Formatter fmt = new Formatter(dest); 31 | decor.formatTo(fmt, 0, 0, 0); 32 | Mockito.verify(dest).append( 33 | MockitoHamcrest.argThat(Matchers.containsString("")) 34 | ); 35 | } 36 | 37 | @Test 38 | void convertsNullToText() throws Exception { 39 | final Formattable decor = new DomDecor(null); 40 | final Appendable dest = Mockito.mock(Appendable.class); 41 | final Formatter fmt = new Formatter(dest); 42 | decor.formatTo(fmt, 0, 0, 0); 43 | Mockito.verify(dest).append("NULL"); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ExceptionDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.IOException; 8 | import java.util.Formattable; 9 | import java.util.Formatter; 10 | import org.hamcrest.Matchers; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.Mockito; 13 | import org.mockito.hamcrest.MockitoHamcrest; 14 | 15 | /** 16 | * Test case for {@link ExceptionDecor}. 17 | * @since 0.1 18 | */ 19 | final class ExceptionDecorTest { 20 | 21 | @Test 22 | void convertsExceptionToText() throws Exception { 23 | final Formattable decor = new ExceptionDecor(new IOException("ouch!")); 24 | final Appendable dest = Mockito.mock(Appendable.class); 25 | final Formatter fmt = new Formatter(dest); 26 | decor.formatTo(fmt, 0, 0, 0); 27 | Mockito.verify(dest).append( 28 | MockitoHamcrest.argThat( 29 | Matchers.allOf( 30 | Matchers.containsString( 31 | "java.io.IOException: ouch!" 32 | ), 33 | Matchers.containsString( 34 | "at com.jcabi.log.ExceptionDecorTest." 35 | ) 36 | ) 37 | ) 38 | ); 39 | } 40 | 41 | @Test 42 | void convertsNullToText() throws Exception { 43 | final Formattable decor = new ExceptionDecor(null); 44 | final Appendable dest = Mockito.mock(Appendable.class); 45 | final Formatter fmt = new Formatter(dest); 46 | decor.formatTo(fmt, 0, 0, 0); 47 | Mockito.verify(dest).append("NULL"); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/FileDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.File; 8 | import java.nio.file.Paths; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.Locale; 12 | import org.hamcrest.MatcherAssert; 13 | import org.hamcrest.Matchers; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.condition.DisabledOnOs; 16 | import org.junit.jupiter.api.condition.EnabledOnOs; 17 | import org.junit.jupiter.api.condition.OS; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.MethodSource; 20 | 21 | /** 22 | * Test case for {@link FileDecor}. 23 | * 24 | * @since 0.1 25 | * @checkstyle ParameterNumberCheck (500 lines) 26 | */ 27 | final class FileDecorTest { 28 | 29 | @Test 30 | @DisabledOnOs(OS.WINDOWS) 31 | void simplyWorksOnUnix() { 32 | MatcherAssert.assertThat( 33 | new Printed(new FileDecor("/tmp/test-me.txt"), 0, 0, 0).toString(), 34 | Matchers.endsWith("test-me.txt") 35 | ); 36 | } 37 | 38 | @Test 39 | @EnabledOnOs(OS.WINDOWS) 40 | void simplyWorksOnWindows() { 41 | MatcherAssert.assertThat( 42 | new Printed(new FileDecor("F:\\hahaha\\b\\foo.txt"), 0, 0, 0).toString(), 43 | Matchers.endsWith("foo.txt") 44 | ); 45 | } 46 | 47 | @DisabledOnOs(OS.WINDOWS) 48 | @ParameterizedTest 49 | @MethodSource("params") 50 | void testPrintsRight(final Object path, final String text, 51 | final int flags, final int width, final int precision) { 52 | Locale.setDefault(Locale.US); 53 | MatcherAssert.assertThat( 54 | new Printed(new FileDecor(path), flags, width, precision), 55 | Matchers.hasToString(text) 56 | ); 57 | } 58 | 59 | @DisabledOnOs(OS.WINDOWS) 60 | @ParameterizedTest 61 | @MethodSource("params") 62 | void testLogsRight(final Object path, final String text, 63 | final int flags, final int width, final int precision) { 64 | Locale.setDefault(Locale.US); 65 | MatcherAssert.assertThat( 66 | new Logged(new FileDecor(path), flags, width, precision), 67 | Matchers.hasToString(text) 68 | ); 69 | } 70 | 71 | /** 72 | * Params for this parametrized test. 73 | * @return Array of arrays of params for ctor 74 | */ 75 | @SuppressWarnings("PMD.UnusedPrivateMethod") 76 | private static Collection params() { 77 | return Arrays.asList( 78 | new Object[][] { 79 | {null, "NULL", 0, 0, 0}, 80 | {"foo.txt", "foo.txt", 0, 0, 0}, 81 | {".", "./", 0, 0, 0}, 82 | {"/tmp", "/tmp", 0, 0, 0}, 83 | {new File("/tmp/x.txt"), "/tmp/x.txt", 0, 0, 0}, 84 | {Paths.get("/a/b/c.txt"), "/a/b/c.txt", 0, 0, 0}, 85 | } 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/LineNumberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.StringWriter; 8 | import java.util.concurrent.TimeUnit; 9 | import org.apache.log4j.Appender; 10 | import org.apache.log4j.Level; 11 | import org.apache.log4j.LogManager; 12 | import org.apache.log4j.PatternLayout; 13 | import org.apache.log4j.WriterAppender; 14 | import org.hamcrest.MatcherAssert; 15 | import org.hamcrest.Matchers; 16 | import org.junit.jupiter.api.Test; 17 | 18 | /** 19 | * Test case for %L pattern. 20 | * If you change this class, you have to care about line number 21 | * in "com.jcabi.log.LineNumberTest:72" 22 | * @since 1.18 23 | */ 24 | final class LineNumberTest { 25 | 26 | /** 27 | * Conversation pattern for test case. 28 | */ 29 | private static final String CONV_PATTERN = "%c:%L"; 30 | 31 | @Test 32 | void testLineNumber() throws Exception { 33 | final PatternLayout layout = new PatternLayout(); 34 | layout.setConversionPattern(LineNumberTest.CONV_PATTERN); 35 | final org.apache.log4j.Logger root = LogManager.getRootLogger(); 36 | final Level level = root.getLevel(); 37 | root.setLevel(Level.INFO); 38 | final StringWriter writer = new StringWriter(); 39 | final Appender appender = new WriterAppender(layout, writer); 40 | root.addAppender(appender); 41 | try { 42 | Logger.info(this, "Test"); 43 | TimeUnit.MILLISECONDS.sleep(1L); 44 | MatcherAssert.assertThat( 45 | writer.toString(), 46 | Matchers.containsString( 47 | "com.jcabi.log.LineNumberTest:241" 48 | ) 49 | ); 50 | } finally { 51 | root.removeAppender(appender); 52 | root.setLevel(level); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ListDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.Locale; 11 | import org.hamcrest.MatcherAssert; 12 | import org.hamcrest.Matchers; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.MethodSource; 15 | 16 | /** 17 | * Test case for {@link ListDecor}. 18 | * 19 | * @since 0.1 20 | * @checkstyle ParameterNumberCheck (500 lines) 21 | */ 22 | final class ListDecorTest { 23 | 24 | @ParameterizedTest 25 | @MethodSource("params") 26 | void testPrintsRight(final Object list, final String text, 27 | final int flags, final int width, final int precision) throws DecorException { 28 | Locale.setDefault(Locale.US); 29 | MatcherAssert.assertThat( 30 | new Printed(new ListDecor(list), flags, width, precision), 31 | Matchers.hasToString(text) 32 | ); 33 | } 34 | 35 | @ParameterizedTest 36 | @MethodSource("params") 37 | void testLogsRight(final Object list, final String text, 38 | final int flags, final int width, final int precision) throws DecorException { 39 | Locale.setDefault(Locale.US); 40 | MatcherAssert.assertThat( 41 | new Logged(new ListDecor(list), flags, width, precision), 42 | Matchers.hasToString(text) 43 | ); 44 | } 45 | 46 | /** 47 | * Params for this parametrized test. 48 | * @return Array of arrays of params for ctor 49 | */ 50 | @SuppressWarnings("PMD.UnusedPrivateMethod") 51 | private static Collection params() { 52 | return Arrays.asList( 53 | new Object[][] { 54 | // @checkstyle MultipleStringLiterals (8 lines) 55 | {null, "[NULL]", 0, 0, 0}, 56 | {new String[] {}, "[]", 0, 0, 0}, 57 | {new String[] {"a"}, "[\"a\"]", 0, 0, 0}, 58 | {new Long[] {2L, 1L}, "[\"2\", \"1\"]", 0, 0, 0}, 59 | {new Object[] {"b", "c"}, "[\"b\", \"c\"]", 0, 0, 0}, 60 | {new Object[] {"foo", 2L}, "[\"foo\", \"2\"]", 0, 0, 0}, 61 | {new ArrayList(0), "[]", 0, 0, 0}, 62 | {Arrays.asList(new String[] {"x"}), "[\"x\"]", 0, 0, 0}, 63 | {Arrays.asList(new Long[] {1L, 2L}), "[\"1\", \"2\"]", 0, 0, 0}, 64 | } 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/Logged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Formattable; 8 | import java.util.FormattableFlags; 9 | 10 | /** 11 | * Logs decor. 12 | * 13 | * @since 0.1 14 | */ 15 | public final class Logged { 16 | 17 | /** 18 | * The decor. 19 | */ 20 | private final transient Formattable decor; 21 | 22 | /** 23 | * Formatting flags. 24 | */ 25 | private final transient int flags; 26 | 27 | /** 28 | * Formatting width. 29 | */ 30 | private final transient int width; 31 | 32 | /** 33 | * Formatting precision. 34 | */ 35 | private final transient int precision; 36 | 37 | /** 38 | * Public ctor. 39 | * @param dcr Decor 40 | * @param flgs Flags 41 | * @param wdt Width 42 | * @param prcs Precision 43 | * @checkstyle ParameterNumber (3 lines) 44 | */ 45 | public Logged(final Formattable dcr, 46 | final int flgs, final int wdt, final int prcs) { 47 | this.decor = dcr; 48 | this.flags = flgs; 49 | this.width = wdt; 50 | this.precision = prcs; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | final StringBuilder format = new StringBuilder(0); 56 | format.append('%'); 57 | if ((this.flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags 58 | .LEFT_JUSTIFY) { 59 | format.append('-'); 60 | } 61 | if (this.width > 0) { 62 | format.append(this.width); 63 | } 64 | if (this.precision > 0) { 65 | format.append('.').append(this.precision); 66 | } 67 | if ((this.flags & FormattableFlags.UPPERCASE) == FormattableFlags 68 | .UPPERCASE) { 69 | format.append('S'); 70 | } else { 71 | format.append('s'); 72 | } 73 | return Logger.format(format.toString(), this.decor); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/LoggerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.OutputStream; 8 | import java.io.OutputStreamWriter; 9 | import java.io.PrintWriter; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.logging.Level; 12 | import org.apache.log4j.LogManager; 13 | import org.hamcrest.MatcherAssert; 14 | import org.hamcrest.Matchers; 15 | import org.junit.jupiter.api.Assertions; 16 | import org.junit.jupiter.api.Test; 17 | 18 | /** 19 | * Test case for {@link Logger}. 20 | * @since 0.1 21 | */ 22 | final class LoggerTest { 23 | 24 | @Test 25 | void detectsLoggerNameCorrectly() { 26 | // not implemented yet 27 | } 28 | 29 | @Test 30 | void detectsNameOfStaticSource() { 31 | // not implemented yet 32 | } 33 | 34 | @Test 35 | void setsLoggingLevel() { 36 | // not implemented yet 37 | } 38 | 39 | @Test 40 | void doesntFormatArraysSinceTheyAreVarArgs() { 41 | Assertions.assertThrows( 42 | IllegalArgumentException.class, 43 | () -> Logger.format("array: %[list]s", new Object[] {"hi", 1}) 44 | ); 45 | } 46 | 47 | @Test 48 | void interpretsArraysAsVarArgs() { 49 | MatcherAssert.assertThat( 50 | Logger.format("array: %s : %d", new Object[] {"hello", 2}), 51 | Matchers.is("array: hello : 2") 52 | ); 53 | } 54 | 55 | @Test 56 | void providesOutputStream() throws Exception { 57 | final OutputStream stream = Logger.stream(Level.INFO, this); 58 | final PrintWriter writer = new PrintWriter( 59 | new OutputStreamWriter(stream, "UTF-8") 60 | ); 61 | writer.print("hello, \u20ac, how're\u040a?\nI'm fine, \u0000\u0007!\n"); 62 | writer.flush(); 63 | writer.close(); 64 | } 65 | 66 | @Test 67 | void throwsWhenParamsLessThanFormatArgs() { 68 | Assertions.assertThrows( 69 | ArrayIndexOutOfBoundsException.class, 70 | () -> Logger.format("String %s Char %c Number %d", "howdy", 'x') 71 | ); 72 | } 73 | 74 | @Test 75 | void throwsWhenParamsMoreThanFormatArgs() { 76 | Assertions.assertThrows( 77 | IllegalArgumentException.class, 78 | () -> Logger.format("String %s Number %d Char %c", "hey", 1, 'x', 2) 79 | ); 80 | } 81 | 82 | @Test 83 | void checksLogLevel() throws Exception { 84 | LogManager.getRootLogger().setLevel(org.apache.log4j.Level.INFO); 85 | TimeUnit.MILLISECONDS.sleep(1L); 86 | MatcherAssert.assertThat( 87 | Logger.isEnabled(Level.INFO, LogManager.getRootLogger()), 88 | Matchers.is(true) 89 | ); 90 | MatcherAssert.assertThat( 91 | Logger.isEnabled(Level.FINEST, LogManager.getRootLogger()), 92 | Matchers.is(false) 93 | ); 94 | } 95 | 96 | @Test 97 | void usesStringAsLoggerName() { 98 | Logger.info("com.jcabi.log...why.not", "hello, %s!", "world!"); 99 | } 100 | 101 | @Test 102 | void findsArgsByPositions() { 103 | final String first = "xyz"; 104 | final String second = "ddd"; 105 | MatcherAssert.assertThat( 106 | Logger.format("first: %s, first again: %1$s %s", first, second), 107 | Matchers.endsWith( 108 | String.format(": %s, first again: %1$s %s", first, second) 109 | ) 110 | ); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/MsDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.FormattableFlags; 10 | import java.util.Locale; 11 | import org.hamcrest.MatcherAssert; 12 | import org.hamcrest.Matchers; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.params.ParameterizedTest; 15 | import org.junit.jupiter.params.provider.MethodSource; 16 | 17 | /** 18 | * Test case for {@link MsDecor}. 19 | * 20 | * @since 0.1 21 | * @checkstyle ParameterNumberCheck (500 lines) 22 | */ 23 | final class MsDecorTest { 24 | 25 | @ParameterizedTest 26 | @MethodSource("params") 27 | void testPrintsRight(final long value, final String text, 28 | final int flags, final int width, final int precision) { 29 | Locale.setDefault(Locale.US); 30 | MatcherAssert.assertThat( 31 | new Printed(new MsDecor(value), flags, width, precision), 32 | Matchers.hasToString(text) 33 | ); 34 | } 35 | 36 | @ParameterizedTest 37 | @MethodSource("params") 38 | void testLogsRight(final long value, final String text, 39 | final int flags, final int width, final int precision) { 40 | Locale.setDefault(Locale.US); 41 | MatcherAssert.assertThat( 42 | new Logged(new MsDecor(value), flags, width, precision), 43 | Matchers.hasToString(text) 44 | ); 45 | } 46 | 47 | @Test 48 | void testPrintsNullRight() { 49 | MatcherAssert.assertThat( 50 | new Logged(new MsDecor(null), 0, 0, 0), 51 | Matchers.hasToString("NULL") 52 | ); 53 | } 54 | 55 | /** 56 | * Params for this parametrized test. 57 | * @return Array of arrays of params for ctor 58 | */ 59 | @SuppressWarnings("PMD.UnusedPrivateMethod") 60 | private static Collection params() { 61 | return Arrays.asList( 62 | new Object[][] { 63 | {13L, "13ms", 0, 0, -1}, 64 | {13L, "13.0ms", 0, 0, 1}, 65 | {1024L, "1s", 0, 0, 0}, 66 | {6001L, "6.0010s", 0, 0, 4}, 67 | {122_001L, " 2MIN", FormattableFlags.UPPERCASE, 6, 0}, 68 | {3_789_003L, "1hr", 0, 0, 0}, 69 | {86_400_000L, "1days", 0, 0, 0}, 70 | {864_000_000L, "10days", 0, 0, 0}, 71 | } 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/MulticolorLayoutTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.apache.commons.text.StringEscapeUtils; 8 | import org.apache.log4j.Level; 9 | import org.apache.log4j.spi.LoggingEvent; 10 | import org.hamcrest.MatcherAssert; 11 | import org.hamcrest.Matchers; 12 | import org.junit.jupiter.api.Assertions; 13 | import org.junit.jupiter.api.Test; 14 | import org.mockito.Mockito; 15 | 16 | /** 17 | * Test case for {@link MulticolorLayout}. 18 | * 19 | * @since 0.1 20 | */ 21 | final class MulticolorLayoutTest { 22 | 23 | /** 24 | * Conversation pattern for test case. 25 | */ 26 | private static final String CONV_PATTERN = "[%color{%p}] %color{%m}"; 27 | 28 | @Test 29 | void transformsLoggingEventToText() { 30 | final MulticolorLayout layout = new MulticolorLayout(); 31 | layout.setConversionPattern(MulticolorLayoutTest.CONV_PATTERN); 32 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 33 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 34 | Mockito.doReturn("hello").when(event).getRenderedMessage(); 35 | MatcherAssert.assertThat( 36 | StringEscapeUtils.escapeJava(layout.format(event)), 37 | Matchers.equalTo( 38 | "[\\u001B[2;37mDEBUG\\u001B[m] \\u001B[2;37mhello\\u001B[m" 39 | ) 40 | ); 41 | } 42 | 43 | @Test 44 | void overwriteDefaultColor() { 45 | final MulticolorLayout layout = new MulticolorLayout(); 46 | layout.setConversionPattern(MulticolorLayoutTest.CONV_PATTERN); 47 | layout.setLevels("INFO:2;10"); 48 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 49 | Mockito.doReturn(Level.INFO).when(event).getLevel(); 50 | Mockito.doReturn("change").when(event).getRenderedMessage(); 51 | MatcherAssert.assertThat( 52 | StringEscapeUtils.escapeJava(layout.format(event)), 53 | Matchers.equalTo( 54 | "[\\u001B[2;10mINFO\\u001B[m] \\u001B[2;10mchange\\u001B[m" 55 | ) 56 | ); 57 | } 58 | 59 | @Test 60 | void rendersCustomConstantColor() { 61 | final MulticolorLayout layout = new MulticolorLayout(); 62 | layout.setConversionPattern("%color-red{%p} %m"); 63 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 64 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 65 | Mockito.doReturn("foo").when(event).getRenderedMessage(); 66 | MatcherAssert.assertThat( 67 | StringEscapeUtils.escapeJava(layout.format(event)), 68 | Matchers.equalTo("\\u001B[31mDEBUG\\u001B[m foo") 69 | ); 70 | } 71 | 72 | @Test 73 | void overwriteCustomConstantColor() { 74 | final MulticolorLayout layout = new MulticolorLayout(); 75 | layout.setConversionPattern("%color-white{%p} %m"); 76 | layout.setColors("white:10"); 77 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 78 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 79 | Mockito.doReturn("const").when(event).getRenderedMessage(); 80 | MatcherAssert.assertThat( 81 | StringEscapeUtils.escapeJava(layout.format(event)), 82 | Matchers.equalTo("\\u001B[10mDEBUG\\u001B[m const") 83 | ); 84 | } 85 | 86 | @Test 87 | void rendersAnsiConstantColor() { 88 | final MulticolorLayout layout = new MulticolorLayout(); 89 | layout.setConversionPattern("%color-0;0;31{%p} %m"); 90 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 91 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 92 | Mockito.doReturn("bar").when(event).getRenderedMessage(); 93 | MatcherAssert.assertThat( 94 | StringEscapeUtils.escapeJava(layout.format(event)), 95 | Matchers.equalTo("\\u001B[0;0;31mDEBUG\\u001B[m bar") 96 | ); 97 | } 98 | 99 | @Test 100 | void throwsOnIllegalColorName() { 101 | Assertions.assertThrows( 102 | IllegalArgumentException.class, 103 | () -> { 104 | final MulticolorLayout layout = new MulticolorLayout(); 105 | layout.setConversionPattern("%color-oops{%p} %m"); 106 | final LoggingEvent event = Mockito.mock(LoggingEvent.class); 107 | Mockito.doReturn(Level.DEBUG).when(event).getLevel(); 108 | Mockito.doReturn("text").when(event).getRenderedMessage(); 109 | layout.format(event); 110 | } 111 | ); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/NanoDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.FormattableFlags; 10 | import java.util.Locale; 11 | import org.hamcrest.MatcherAssert; 12 | import org.hamcrest.Matchers; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.params.ParameterizedTest; 15 | import org.junit.jupiter.params.provider.MethodSource; 16 | 17 | /** 18 | * Test case for {@link NanoDecor}. 19 | 20 | * @since 0.1 21 | * @checkstyle ParameterNumberCheck (500 lines) 22 | */ 23 | final class NanoDecorTest { 24 | 25 | @ParameterizedTest 26 | @MethodSource("params") 27 | void testPrintsRight(final long nano, final String text, 28 | final int flags, final int width, final int precision) { 29 | Locale.setDefault(Locale.US); 30 | MatcherAssert.assertThat( 31 | new Printed(new NanoDecor(nano), flags, width, precision), 32 | Matchers.hasToString(text) 33 | ); 34 | } 35 | 36 | @ParameterizedTest 37 | @MethodSource("params") 38 | void testLogsRight(final long nano, final String text, 39 | final int flags, final int width, final int precision) { 40 | Locale.setDefault(Locale.US); 41 | MatcherAssert.assertThat( 42 | new Logged(new NanoDecor(nano), flags, width, precision), 43 | Matchers.hasToString(text) 44 | ); 45 | } 46 | 47 | @Test 48 | void testPrintsNullRight() { 49 | MatcherAssert.assertThat( 50 | new Logged(new NanoDecor(null), 0, 0, 0), 51 | Matchers.hasToString("NULL") 52 | ); 53 | } 54 | 55 | /** 56 | * Params for this parametrized test. 57 | * @return Array of arrays of params for ctor 58 | */ 59 | @SuppressWarnings("PMD.UnusedPrivateMethod") 60 | private static Collection params() { 61 | return Arrays.asList( 62 | new Object[][] { 63 | {13L, "13ns", 0, 0, -1}, 64 | {13L, "13.0ns", 0, 0, 1}, 65 | {25L, "25.00ns", 0, 0, 2}, 66 | {234L, "234.0ns", 0, 0, 1}, 67 | {1024L, "1µs", 0, 0, 0}, 68 | {1056L, "1.056µs", 0, 0, 3}, 69 | {9022L, "9.02µs", 0, 0, 2}, 70 | {53_111L, "53.11µs ", FormattableFlags.LEFT_JUSTIFY, 10, 2}, 71 | {53_156L, " 53µs", 0, 7, 0}, 72 | {87_090_432L, " 87ms", 0, 6, 0}, 73 | {87_090_543L, "87.09ms", 0, 0, 2}, 74 | {87_090_548L, "87.0905ms", 0, 0, 4}, 75 | {6_001_001_001L, "6.0010s", 0, 0, 4}, 76 | {122_001_001_001L, " 2MIN", FormattableFlags.UPPERCASE, 6, 0}, 77 | {3_789_001_001_001L, "63.15002min", 0, 0, 5}, 78 | {3_789_002_002_002L, "63.2min", 0, 0, 1}, 79 | {3_789_003_003_003L, "63min", 0, 0, 0}, 80 | {342_000_004_004_004L, "5700min", 0, 0, 0}, 81 | } 82 | ); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ObjectDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.Locale; 10 | import org.hamcrest.MatcherAssert; 11 | import org.hamcrest.Matchers; 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | 15 | /** 16 | * Test case for {@link ObjectDecor}. 17 | * 18 | * @since 0.1 19 | * @checkstyle ParameterNumberCheck (500 lines) 20 | */ 21 | final class ObjectDecorTest { 22 | 23 | @ParameterizedTest 24 | @MethodSource("params") 25 | void testPrintsRight(final Object obj, final String text, 26 | final int flags, final int width, final int precision) { 27 | Locale.setDefault(Locale.US); 28 | MatcherAssert.assertThat( 29 | new Printed(new ObjectDecor(obj), flags, width, precision), 30 | Matchers.hasToString(Matchers.containsString(text)) 31 | ); 32 | } 33 | 34 | @ParameterizedTest 35 | @MethodSource("params") 36 | void testLogsRight(final Object obj, final String text, 37 | final int flags, final int width, final int precision) { 38 | Locale.setDefault(Locale.US); 39 | MatcherAssert.assertThat( 40 | new Logged(new ObjectDecor(obj), flags, width, precision), 41 | Matchers.hasToString(Matchers.containsString(text)) 42 | ); 43 | } 44 | 45 | /** 46 | * Params for this parametrized test. 47 | * @return Array of arrays of params for ctor 48 | */ 49 | @SuppressWarnings("PMD.UnusedPrivateMethod") 50 | private static Collection params() { 51 | return Arrays.asList( 52 | new Object[][] { 53 | {null, "NULL", 0, 0, 0}, 54 | {new SecretDecor("x"), "{secret: \"x\"", 0, 0, 0}, 55 | {new ObjectDecorTest.Foo(1, "one"), "{num: \"1\", name: \"one\"", 0, 0, 0}, 56 | { 57 | new Object[]{ 58 | new ObjectDecorTest.Foo(0, "zero"), 59 | new ObjectDecorTest.Foo(2, "two"), 60 | }, 61 | "[{num: \"0\", name: \"zero\"", 62 | 0, 0, 0, 63 | }, 64 | { 65 | new Object[]{ 66 | new ObjectDecorTest.Foo(0, "abc"), 67 | new ObjectDecorTest.Foo(2, "cde"), 68 | }, 69 | ", {num: \"2\", name: \"cde\"", 70 | 0, 0, 0, 71 | }, 72 | { 73 | new Object[] {new Object[] {null}, }, "[[NULL", 0, 0, 0, 74 | }, 75 | } 76 | ); 77 | } 78 | 79 | /** 80 | * Test class for displaying object contents. 81 | * 82 | * @since 0.1 83 | */ 84 | private static final class Foo { 85 | /** 86 | * The number. 87 | */ 88 | @SuppressWarnings("unused") 89 | private final transient int num; 90 | 91 | /** 92 | * The name. 93 | */ 94 | @SuppressWarnings("unused") 95 | private final transient String name; 96 | 97 | /** 98 | * Ctor. 99 | * @param number The number 100 | * @param nme The name 101 | */ 102 | Foo(final int number, final String nme) { 103 | this.num = number; 104 | this.name = nme; 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ParseableInformationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Map; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.hamcrest.MatcherAssert; 10 | import org.hamcrest.Matchers; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.Test; 13 | 14 | /** 15 | * ParseableInformation test case. 16 | * @since 0.18 17 | */ 18 | class ParseableInformationTest { 19 | 20 | /** 21 | * ParseableInformation can parse if the information correctly if is using 22 | * the right pattern. 23 | */ 24 | @Test 25 | @SuppressWarnings("PMD.UseConcurrentHashMap") 26 | final void parsesTheInformationCorrectly() { 27 | final Map parsed = new ParseableInformation( 28 | "red:10,black:20" 29 | ).information(); 30 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("red", "10")); 31 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("black", "20")); 32 | } 33 | 34 | /** 35 | * ParseableInformation can throw an an exception when parsing wrong info. 36 | */ 37 | @Test 38 | final void throwsAnExceptionWhenParsingSomethingWrong() { 39 | final String white = "white"; 40 | try { 41 | new ParseableInformation(white).information(); 42 | Assertions.fail("Should never enter this assert!"); 43 | } catch (final IllegalStateException ex) { 44 | MatcherAssert.assertThat( 45 | ex.getMessage(), Matchers.equalTo( 46 | String.format( 47 | StringUtils.join( 48 | "Information is not using the pattern ", 49 | "KEY1:VALUE,KEY2:VALUE %s" 50 | ), white 51 | ) 52 | ) 53 | ); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/ParseableLevelInformationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Map; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.hamcrest.MatcherAssert; 10 | import org.hamcrest.Matchers; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.Test; 13 | 14 | /** 15 | * ParseableLevelInformation test case. 16 | * @since 0.18 17 | */ 18 | class ParseableLevelInformationTest { 19 | 20 | /** 21 | * ParseableLevelInformation can parse the information correctly when it's 22 | * with the right pattern. 23 | */ 24 | @Test 25 | @SuppressWarnings("PMD.UseConcurrentHashMap") 26 | final void parsesCorrectlyTheInformation() { 27 | final Map parsed = new ParseableLevelInformation( 28 | "INFO:2;10,WARN:2;32" 29 | ).information(); 30 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("INFO", "2;10")); 31 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("WARN", "2;32")); 32 | } 33 | 34 | /** 35 | * ParseableLevelInformation can throw an exception when information is 36 | * not with the right pattern. 37 | */ 38 | @Test 39 | final void throwsAnExceptionWhenParsingIncorrectInformation() { 40 | final String wrong = "INFO;10,WARN;32"; 41 | try { 42 | new ParseableLevelInformation(wrong).information(); 43 | Assertions.fail("Something was wrong"); 44 | } catch (final IllegalStateException ex) { 45 | MatcherAssert.assertThat( 46 | ex.getMessage(), Matchers.equalTo( 47 | String.format( 48 | StringUtils.join( 49 | "Information is not using the pattern ", 50 | "KEY1:VALUE,KEY2:VALUE %s" 51 | ), wrong 52 | ) 53 | ) 54 | ); 55 | } 56 | } 57 | 58 | /** 59 | * ParseableLevelInformation can throw an exception when passing information 60 | * with a wrong type of level. 61 | */ 62 | @Test 63 | final void throwsAnExceptionWhenParsingWrongLevelType() { 64 | try { 65 | new ParseableLevelInformation( 66 | "INFO:2;10,EXTREME:2;32" 67 | ).information(); 68 | Assertions.fail(""); 69 | } catch (final IllegalStateException ex) { 70 | MatcherAssert.assertThat( 71 | ex.getMessage(), 72 | Matchers.equalTo("Unknown level 'EXTREME'") 73 | ); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/PreFormatterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.hamcrest.MatcherAssert; 8 | import org.hamcrest.Matchers; 9 | import org.junit.jupiter.api.Test; 10 | 11 | /** 12 | * Test case for {@link PreFormatter}. 13 | * @since 0.1 14 | */ 15 | final class PreFormatterTest { 16 | 17 | /** 18 | * PreFormatter can format simple texts. 19 | */ 20 | @Test 21 | void decoratesArguments() { 22 | final PreFormatter pre = new PreFormatter( 23 | "%[com.jcabi.log.DecorMocker]-5.2f and %1$+.6f", 24 | 1.0d 25 | ); 26 | MatcherAssert.assertThat( 27 | pre.getFormat(), 28 | Matchers.equalTo("%-5.2f and %1$+.6f") 29 | ); 30 | MatcherAssert.assertThat( 31 | pre.getArguments()[0], 32 | Matchers.instanceOf(DecorMocker.class) 33 | ); 34 | } 35 | 36 | /** 37 | * PreFormatter can handle missed decors. 38 | */ 39 | @Test 40 | void formatsEvenWithMissedDecors() { 41 | final PreFormatter pre = 42 | new PreFormatter("ouch: %[missed]s", "test"); 43 | MatcherAssert.assertThat( 44 | pre.getFormat(), 45 | Matchers.equalTo("ouch: %s") 46 | ); 47 | MatcherAssert.assertThat( 48 | pre.getArguments()[0], 49 | Matchers.instanceOf(String.class) 50 | ); 51 | } 52 | 53 | /** 54 | * PreFormatter can handle directly provided decors. 55 | */ 56 | @Test 57 | void formatsWithDirectlyProvidedDecors() { 58 | final DecorMocker decor = new DecorMocker("a"); 59 | final PreFormatter pre = new PreFormatter("test: %s", decor); 60 | MatcherAssert.assertThat( 61 | pre.getArguments()[0], 62 | Matchers.equalTo(decor) 63 | ); 64 | } 65 | 66 | /** 67 | * PreFormatter can handle new line specifier. 68 | */ 69 | @Test 70 | void handleNewLineSpecifier() { 71 | final String fmt = "%s%n%s"; 72 | final Object[] args = {"new", "line"}; 73 | final PreFormatter pre = new PreFormatter(fmt, args); 74 | MatcherAssert.assertThat( 75 | pre.getFormat(), 76 | Matchers.is(fmt) 77 | ); 78 | MatcherAssert.assertThat( 79 | pre.getArguments(), 80 | Matchers.is(args) 81 | ); 82 | } 83 | 84 | /** 85 | * PreFormatter can handle percent specifier. 86 | */ 87 | @Test 88 | void handlePercentSpecifier() { 89 | final String fmt = "%s%%"; 90 | final Object[] args = {"percent: "}; 91 | final PreFormatter pre = new PreFormatter(fmt, args); 92 | MatcherAssert.assertThat( 93 | pre.getFormat(), 94 | Matchers.is(fmt) 95 | ); 96 | MatcherAssert.assertThat( 97 | pre.getArguments(), 98 | Matchers.is(args) 99 | ); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/Printed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.util.Formattable; 9 | import java.util.Formatter; 10 | 11 | /** 12 | * Prints decor. 13 | * 14 | * @since 0.1 15 | */ 16 | public final class Printed { 17 | 18 | /** 19 | * The decor. 20 | */ 21 | private final transient Formattable decor; 22 | 23 | /** 24 | * Formatting flags. 25 | */ 26 | private final transient int flags; 27 | 28 | /** 29 | * Formatting width. 30 | */ 31 | private final transient int width; 32 | 33 | /** 34 | * Formatting precision. 35 | */ 36 | private final transient int precision; 37 | 38 | /** 39 | * Public ctor. 40 | * @param dcr Decor 41 | * @param flgs Flags 42 | * @param wdt Width 43 | * @param prcs Precision 44 | * @checkstyle ParameterNumber (3 lines) 45 | */ 46 | public Printed(final Formattable dcr, 47 | final int flgs, final int wdt, final int prcs) { 48 | this.decor = dcr; 49 | this.flags = flgs; 50 | this.width = wdt; 51 | this.precision = prcs; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 57 | final Formatter fmt = new Formatter(baos); 58 | this.decor.formatTo(fmt, this.flags, this.width, this.precision); 59 | fmt.flush(); 60 | return baos.toString(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/SecretDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.FormattableFlags; 10 | import java.util.Locale; 11 | import org.hamcrest.MatcherAssert; 12 | import org.hamcrest.Matchers; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.MethodSource; 15 | 16 | /** 17 | * Test case for {@link SecretDecor}. 18 | * 19 | * @since 0.1 20 | * @checkstyle ParameterNumberCheck (500 lines) 21 | */ 22 | final class SecretDecorTest { 23 | 24 | @ParameterizedTest 25 | @MethodSource("params") 26 | void testPrintsRight(final Object list, final String text, 27 | final int flags, final int width, final int precision) { 28 | Locale.setDefault(Locale.US); 29 | MatcherAssert.assertThat( 30 | new Printed(new SecretDecor(list), flags, width, precision), 31 | Matchers.hasToString(text) 32 | ); 33 | } 34 | 35 | @ParameterizedTest 36 | @MethodSource("params") 37 | void testLogsRight(final Object list, final String text, 38 | final int flags, final int width, final int precision) { 39 | Locale.setDefault(Locale.US); 40 | MatcherAssert.assertThat( 41 | new Logged(new SecretDecor(list), flags, width, precision), 42 | Matchers.hasToString(text) 43 | ); 44 | } 45 | 46 | /** 47 | * Params for this parametrized test. 48 | * @return Array of arrays of params for ctor 49 | */ 50 | @SuppressWarnings("PMD.UnusedPrivateMethod") 51 | private static Collection params() { 52 | return Arrays.asList( 53 | new Object[][] { 54 | {"testing", "t***g", 0, 0, 0}, 55 | {"ouch", "o***h ", FormattableFlags.LEFT_JUSTIFY, 7, 5}, 56 | {"x", " X***X", FormattableFlags.UPPERCASE, 6, 0}, 57 | {null, "NULL", FormattableFlags.UPPERCASE, 6, 0}, 58 | } 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/SizeDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.FormattableFlags; 10 | import java.util.Locale; 11 | import org.hamcrest.MatcherAssert; 12 | import org.hamcrest.Matchers; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.params.ParameterizedTest; 15 | import org.junit.jupiter.params.provider.MethodSource; 16 | 17 | /** 18 | * Test case for {@link SizeDecor}. 19 | * 20 | * @since 0.1 21 | * @checkstyle ParameterNumberCheck (500 lines) 22 | */ 23 | final class SizeDecorTest { 24 | 25 | @ParameterizedTest 26 | @MethodSource("params") 27 | void testPrintsRight(final long size, final String text, 28 | final int flags, final int width, final int precision) { 29 | Locale.setDefault(Locale.US); 30 | MatcherAssert.assertThat( 31 | new Printed(new SizeDecor(size), flags, width, precision), 32 | Matchers.hasToString(text) 33 | ); 34 | } 35 | 36 | @ParameterizedTest 37 | @MethodSource("params") 38 | void testLogsRight(final long size, final String text, 39 | final int flags, final int width, final int precision) { 40 | Locale.setDefault(Locale.US); 41 | MatcherAssert.assertThat( 42 | new Logged(new SizeDecor(size), flags, width, precision), 43 | Matchers.hasToString(text) 44 | ); 45 | } 46 | 47 | @Test 48 | void testPrintsNullRight() { 49 | MatcherAssert.assertThat( 50 | new Logged(new SizeDecor(null), 0, 0, 0), 51 | Matchers.hasToString("NULL") 52 | ); 53 | } 54 | 55 | /** 56 | * Params for this parametrized test. 57 | * @return Array of arrays of params for ctor 58 | */ 59 | @SuppressWarnings("PMD.UnusedPrivateMethod") 60 | private static Collection params() { 61 | return Arrays.asList( 62 | new Object[][] { 63 | {1L, "1b", 0, 0, 0}, 64 | {123L, " 123b", 0, 6, 0}, 65 | {1024L, "1.000Kb", 0, 0, 3}, 66 | {5120L, "5Kb", 0, 0, 0}, 67 | {12_345L, "12.056Kb", 0, 0, 3}, 68 | {12_345L, "12.1Kb ", FormattableFlags.LEFT_JUSTIFY, 8, 1}, 69 | {98_765_432L, "94.190MB", FormattableFlags.UPPERCASE, 0, 3}, 70 | {98_765_432L, "94.190Mb", 0, 0, 3}, 71 | {90L * 1024L * 1024L * 1024L, "90Gb", 0, 0, 0}, 72 | {13L * 1024L * 1024L * 1024L * 1024L, "13Tb", 0, 0, 0}, 73 | {33L * 1024L * 1024L * 1024L * 1024L * 1024L, "33Pb", 0, 0, 0}, 74 | {3L * 1024L * 1024L * 1024L * 1024L * 1024L * 1024L, "3Eb", 0, 0, 0}, 75 | } 76 | ); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/SupplierLoggerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.apache.log4j.Level; 8 | import org.hamcrest.MatcherAssert; 9 | import org.hamcrest.Matchers; 10 | import org.junit.jupiter.api.Disabled; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** 14 | * Test case for {@link SupplierLogger}. 15 | * @since 0.18 16 | * @todo #100:30min Some tests here are ignored since they conflict 17 | * in multi-threading run. I don't know exactly how to fix them, 18 | * but we need to fix and remove the "Ignore" annotations. 19 | */ 20 | @SuppressWarnings("PMD.MoreThanOneLogger") 21 | final class SupplierLoggerTest { 22 | 23 | @Test 24 | void debugIsDisabled() { 25 | final String name = "nodebug"; 26 | final String appender = "nodebugapp"; 27 | final org.apache.log4j.Logger logger = this.loggerForTest( 28 | name, appender, Level.ERROR 29 | ); 30 | SupplierLogger.debug( 31 | name, "Debug disabled: %s", 32 | (Supplier) () -> "test1" 33 | ); 34 | MatcherAssert.assertThat( 35 | ((UnitTestAppender) logger.getAppender(appender)).output(), 36 | Matchers.emptyString() 37 | ); 38 | } 39 | 40 | @Test 41 | @Disabled 42 | void debugIsEnabled() { 43 | final String name = "debugen"; 44 | final String appender = "debugapp"; 45 | final org.apache.log4j.Logger logger = this.loggerForTest( 46 | name, appender, Level.DEBUG 47 | ); 48 | final String text = "test2"; 49 | SupplierLogger.debug( 50 | name, "Debug enabled: %s", 51 | (Supplier) () -> text 52 | ); 53 | MatcherAssert.assertThat( 54 | ((UnitTestAppender) logger.getAppender(appender)).output(), 55 | Matchers.containsString(text) 56 | ); 57 | } 58 | 59 | @Test 60 | void traceIsDisabled() { 61 | final String name = "notrace"; 62 | final String appender = "notraceapp"; 63 | final org.apache.log4j.Logger logger = this.loggerForTest( 64 | name, appender, Level.ERROR 65 | ); 66 | SupplierLogger.trace( 67 | name, "Trace disabled: %s", 68 | (Supplier) () -> "test3" 69 | ); 70 | MatcherAssert.assertThat( 71 | ((UnitTestAppender) logger.getAppender(appender)).output(), 72 | Matchers.emptyString() 73 | ); 74 | } 75 | 76 | @Test 77 | void traceIsEnabled() { 78 | final String name = "enabledtrace"; 79 | final String appender = "traceapp"; 80 | final org.apache.log4j.Logger logger = this.loggerForTest( 81 | name, appender, Level.TRACE 82 | ); 83 | final String text = "text4"; 84 | SupplierLogger.trace( 85 | name, "Trace enabled: %s", 86 | (Supplier) () -> text 87 | ); 88 | MatcherAssert.assertThat( 89 | ((UnitTestAppender) logger.getAppender(appender)).output(), 90 | Matchers.containsString(text) 91 | ); 92 | } 93 | 94 | @Test 95 | void warnIsDisabled() { 96 | final String name = "nowarn"; 97 | final String appender = "nowarnapp"; 98 | final org.apache.log4j.Logger logger = this.loggerForTest( 99 | name, appender, Level.ERROR 100 | ); 101 | SupplierLogger.warn( 102 | name, "Warn disabled: %s", 103 | (Supplier) () -> "test5" 104 | ); 105 | MatcherAssert.assertThat( 106 | ((UnitTestAppender) logger.getAppender(appender)).output(), 107 | Matchers.emptyString() 108 | ); 109 | } 110 | 111 | @Test 112 | @Disabled 113 | void warnIsEnabled() { 114 | final String name = "enwarn"; 115 | final String appender = "warnapp"; 116 | final org.apache.log4j.Logger logger = this.loggerForTest( 117 | name, appender, Level.WARN 118 | ); 119 | final String text = "test6"; 120 | SupplierLogger.warn( 121 | name, "Warn enabled: %s", 122 | (Supplier) () -> text 123 | ); 124 | MatcherAssert.assertThat( 125 | ((UnitTestAppender) logger.getAppender(appender)).output(), 126 | Matchers.containsString(text) 127 | ); 128 | } 129 | 130 | @Test 131 | void infoIsDisabled() { 132 | final String name = "noinfo"; 133 | final String appender = "noinfoapp"; 134 | final org.apache.log4j.Logger logger = this.loggerForTest( 135 | name, appender, Level.WARN 136 | ); 137 | SupplierLogger.info( 138 | name, "Info disabled: %s", 139 | (Supplier) () -> "test7" 140 | ); 141 | MatcherAssert.assertThat( 142 | ((UnitTestAppender) logger.getAppender(appender)).output(), 143 | Matchers.emptyString() 144 | ); 145 | } 146 | 147 | @Test 148 | @Disabled 149 | void infoIsEnabled() { 150 | final String name = "withinfo"; 151 | final String appender = "infoapp"; 152 | final org.apache.log4j.Logger logger = this.loggerForTest( 153 | name, appender, Level.INFO 154 | ); 155 | final String text = "text8"; 156 | SupplierLogger.info( 157 | name, "Info enabled: %s", 158 | (Supplier) () -> text 159 | ); 160 | MatcherAssert.assertThat( 161 | ((UnitTestAppender) logger.getAppender(appender)).output(), 162 | Matchers.containsString(text) 163 | ); 164 | } 165 | 166 | /** 167 | * Builds a logger for each test method. 168 | * @param name Logger's name 169 | * @param appender Appender's name 170 | * @param level Logging level 171 | * @return Logger for test 172 | */ 173 | private org.apache.log4j.Logger loggerForTest( 174 | final String name, final String appender, final Level level) { 175 | final org.apache.log4j.Logger logger = org.apache.log4j.Logger 176 | .getLogger(name); 177 | final UnitTestAppender app = new UnitTestAppender(appender); 178 | app.activateOptions(); 179 | logger.addAppender(app); 180 | logger.setLevel(level); 181 | return logger; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/TextDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.Formattable; 11 | import java.util.Formatter; 12 | import java.util.Locale; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.hamcrest.MatcherAssert; 15 | import org.hamcrest.Matchers; 16 | import org.junit.jupiter.api.Assumptions; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.MethodSource; 20 | 21 | /** 22 | * Test case for {@link TextDecor}. 23 | * 24 | * @since 0.1 25 | * @checkstyle ParameterNumberCheck (500 lines) 26 | */ 27 | final class TextDecorTest { 28 | 29 | @ParameterizedTest 30 | @MethodSource("params") 31 | void testPrintsRight(final String obj, final String text, 32 | final int flags, final int width, final int precision) { 33 | Assumptions.assumeTrue("UTF-8".equals(Charset.defaultCharset().name())); 34 | Locale.setDefault(Locale.US); 35 | MatcherAssert.assertThat( 36 | new Printed(new TextDecor(obj), flags, width, precision), 37 | Matchers.hasToString(text) 38 | ); 39 | } 40 | 41 | @ParameterizedTest 42 | @MethodSource("params") 43 | void testLogsRight(final String obj, final String text, 44 | final int flags, final int width, final int precision) { 45 | Assumptions.assumeTrue("UTF-8".equals(Charset.defaultCharset().name())); 46 | Locale.setDefault(Locale.US); 47 | MatcherAssert.assertThat( 48 | new Logged(new TextDecor(obj), flags, width, precision), 49 | Matchers.hasToString(text) 50 | ); 51 | } 52 | 53 | /** 54 | * Test for a long text. 55 | */ 56 | @Test 57 | void compressesLongText() { 58 | final int len = 1000; 59 | final String text = StringUtils.repeat('x', len); 60 | final Formattable fmt = new TextDecor(text); 61 | final StringBuilder output = new StringBuilder(100); 62 | fmt.formatTo(new Formatter(output), 0, 0, 0); 63 | MatcherAssert.assertThat( 64 | output.length(), 65 | Matchers.describedAs( 66 | output.toString(), 67 | Matchers.equalTo(TextDecor.MAX) 68 | ) 69 | ); 70 | } 71 | 72 | /** 73 | * Params for this parametrized test. 74 | * @return Array of arrays of params for ctor 75 | */ 76 | @SuppressWarnings( 77 | { 78 | "PMD.AvoidDuplicateLiterals", 79 | "PMD.UnusedPrivateMethod" 80 | } 81 | ) 82 | private static Collection params() { 83 | return Arrays.asList( 84 | new Object[][] { 85 | // @checkstyle MultipleStringLiterals (1 line) 86 | {"simple text", "simple text", 0, 0, 0}, 87 | {null, "NULL", 0, 0, 0}, 88 | // @checkstyle MultipleStringLiteralsCheck (1 line) 89 | {"\u0433!", "\u0433!", 0, 0, 0}, 90 | } 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/TypeDecorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.Locale; 10 | import org.hamcrest.MatcherAssert; 11 | import org.hamcrest.Matchers; 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | 15 | /** 16 | * Test case for {@link TypeDecor}. 17 | * 18 | * @since 0.1 19 | * @checkstyle ParameterNumberCheck (500 lines) 20 | */ 21 | final class TypeDecorTest { 22 | 23 | @ParameterizedTest 24 | @MethodSource("params") 25 | void testPrintsRight(final Object list, final String text, 26 | final int flags, final int width, final int precision) { 27 | Locale.setDefault(Locale.US); 28 | MatcherAssert.assertThat( 29 | new Printed(new TypeDecor(list), flags, width, precision), 30 | Matchers.hasToString(text) 31 | ); 32 | } 33 | 34 | @ParameterizedTest 35 | @MethodSource("params") 36 | void testLogsRight(final Object list, final String text, 37 | final int flags, final int width, final int precision) { 38 | Locale.setDefault(Locale.US); 39 | MatcherAssert.assertThat( 40 | new Logged(new TypeDecor(list), flags, width, precision), 41 | Matchers.hasToString(text) 42 | ); 43 | } 44 | 45 | /** 46 | * Params for this parametrized test. 47 | * @return Array of arrays of params for ctor 48 | */ 49 | @SuppressWarnings("PMD.UnusedPrivateMethod") 50 | private static Collection params() { 51 | return Arrays.asList( 52 | new Object[][] { 53 | {"testing", "java.lang.String", 0, 0, 0}, 54 | {null, "NULL", 0, 0, 0}, 55 | {1.0d, "java.lang.Double", 0, 0, 0}, 56 | } 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/UnitTestAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package com.jcabi.log; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import org.apache.log4j.PatternLayout; 11 | import org.apache.log4j.WriterAppender; 12 | 13 | /** 14 | * Log4j appender for unit tests. Normally, we could use 15 | * slf4j-test, but we 16 | * have log4j in the classpath anyway, for {@link MulticolorLayout}. 17 | * @since 0.18 18 | */ 19 | final class UnitTestAppender extends WriterAppender { 20 | 21 | /** 22 | * OutputStream where this Appender writes. 23 | */ 24 | private final transient ByteArrayOutputStream logs; 25 | 26 | /** 27 | * Ctor. 28 | * @param name The appender's name 29 | */ 30 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") 31 | UnitTestAppender(final String name) { 32 | super(); 33 | this.setName(name); 34 | this.logs = new ByteArrayOutputStream(); 35 | } 36 | 37 | /** 38 | * Prepares the appender for use. 39 | */ 40 | public void activateOptions() { 41 | this.setWriter(this.createWriter(this.logs)); 42 | this.setLayout(new PatternLayout("%d %c{1} - %m%n")); 43 | super.activateOptions(); 44 | } 45 | 46 | /** 47 | * Return the logged messages. 48 | * @return String logs 49 | */ 50 | public String output() { 51 | return new String(this.logs.toByteArray(), StandardCharsets.UTF_8); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/VerboseCallableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | /** 11 | * Test case for {@link VerboseCallable}. 12 | * @since 0.16 13 | */ 14 | @SuppressWarnings({ "PMD.DoNotUseThreads", "PMD.TooManyMethods" }) 15 | final class VerboseCallableTest { 16 | 17 | /** 18 | * VerboseCallable can log exceptions inside Callable. 19 | */ 20 | @Test 21 | void logsExceptionsInCallable() { 22 | Assertions.assertThrows( 23 | IllegalArgumentException.class, 24 | () -> new VerboseCallable( 25 | () -> { 26 | throw new IllegalArgumentException("oops"); 27 | } 28 | ).call() 29 | ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/VerboseRunnableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | import org.hamcrest.MatcherAssert; 14 | import org.hamcrest.Matchers; 15 | import org.junit.jupiter.api.Assertions; 16 | import org.junit.jupiter.api.Test; 17 | 18 | /** 19 | * Test case for {@link VerboseRunnable}. 20 | * @since 0.1 21 | */ 22 | @SuppressWarnings({ "PMD.DoNotUseThreads", "PMD.TooManyMethods" }) 23 | final class VerboseRunnableTest { 24 | 25 | @Test 26 | void logsExceptionsInRunnable() { 27 | Assertions.assertThrows( 28 | IllegalArgumentException.class, 29 | () -> new VerboseRunnable( 30 | (Runnable) () -> { 31 | throw new IllegalArgumentException("oops"); 32 | } 33 | ).run() 34 | ); 35 | } 36 | 37 | @Test 38 | void swallowsExceptionsInRunnable() { 39 | new VerboseRunnable( 40 | (Runnable) () -> { 41 | throw new IllegalArgumentException("boom"); 42 | }, 43 | true 44 | ).run(); 45 | } 46 | 47 | @Test 48 | void swallowsExceptionsInCallable() { 49 | new VerboseRunnable( 50 | () -> { 51 | throw new IllegalArgumentException("boom-2"); 52 | }, 53 | true 54 | ).run(); 55 | } 56 | 57 | @Test 58 | void translatesToStringFromUnderlyingRunnable() { 59 | final String text = "some text abc"; 60 | final Runnable verbose = new VerboseRunnable( 61 | new Runnable() { 62 | @Override 63 | public void run() { 64 | assert true; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return text; 70 | } 71 | } 72 | ); 73 | MatcherAssert.assertThat( 74 | verbose, 75 | Matchers.hasToString(Matchers.containsString(text)) 76 | ); 77 | } 78 | 79 | @Test 80 | void translatesToStringFromUnderlyingCallable() { 81 | final String text = "some text abc-2"; 82 | final Runnable verbose = new VerboseRunnable( 83 | new Callable() { 84 | @Override 85 | public Void call() { 86 | return null; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return text; 92 | } 93 | }, 94 | true 95 | ); 96 | MatcherAssert.assertThat( 97 | verbose, 98 | Matchers.hasToString(Matchers.containsString(text)) 99 | ); 100 | } 101 | 102 | @Test 103 | void preservesInterruptedStatus() throws Exception { 104 | final ScheduledExecutorService svc = 105 | Executors.newSingleThreadScheduledExecutor(); 106 | final AtomicReference thread = new AtomicReference<>(); 107 | final AtomicInteger runs = new AtomicInteger(); 108 | svc.scheduleWithFixedDelay( 109 | new VerboseRunnable( 110 | () -> { 111 | runs.addAndGet(1); 112 | thread.set(Thread.currentThread()); 113 | TimeUnit.HOURS.sleep(1L); 114 | return null; 115 | }, 116 | true, 117 | false 118 | ), 119 | 1L, 1L, 120 | TimeUnit.MICROSECONDS 121 | ); 122 | while (thread.get() == null) { 123 | TimeUnit.MILLISECONDS.sleep(1L); 124 | } 125 | thread.get().interrupt(); 126 | TimeUnit.SECONDS.sleep(1L); 127 | svc.shutdown(); 128 | MatcherAssert.assertThat(runs.get(), Matchers.is(1)); 129 | MatcherAssert.assertThat( 130 | svc.awaitTermination(1L, TimeUnit.SECONDS), 131 | Matchers.is(true) 132 | ); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/VerboseThreadsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | package com.jcabi.log; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | import java.util.concurrent.TimeUnit; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** 14 | * Test case for {@link VerboseThreads}. 15 | * @since 0.1 16 | */ 17 | @SuppressWarnings("PMD.DoNotUseThreads") 18 | final class VerboseThreadsTest { 19 | 20 | @Test 21 | void instantiatesThreadsOnDemand() throws Exception { 22 | final ExecutorService service = Executors 23 | .newSingleThreadExecutor(new VerboseThreads("foo")); 24 | service.execute( 25 | () -> { 26 | throw new IllegalArgumentException("oops"); 27 | } 28 | ); 29 | TimeUnit.SECONDS.sleep(1L); 30 | service.shutdown(); 31 | } 32 | 33 | @Test 34 | void logsWhenThreadsAreNotDying() throws Exception { 35 | final ExecutorService service = Executors 36 | .newSingleThreadExecutor(new VerboseThreads(this)); 37 | final Future future = service.submit( 38 | (Runnable) () -> { 39 | throw new IllegalArgumentException("boom"); 40 | } 41 | ); 42 | while (!future.isDone()) { 43 | TimeUnit.SECONDS.sleep(1L); 44 | } 45 | service.shutdown(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/jcabi/log/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | /** 7 | * Convenient utils, tests. 8 | */ 9 | package com.jcabi.log; 10 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | # Set root logger level to DEBUG and its only appender to CONSOLE 5 | log4j.rootLogger=WARN, CONSOLE 6 | 7 | # "Console" is set to be a ConsoleAppender. 8 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 9 | log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout 10 | log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%p}] %t %c: %m%n 11 | 12 | # Application-specific logging 13 | log4j.logger.com.jcabi=DEBUG 14 | --------------------------------------------------------------------------------